Refactor BCMath (#14076)

Optimized the order of structure members and the process of converting
strings to bc_num structures.

closes #14076
This commit is contained in:
Saki Takamachi 2024-04-29 12:22:27 +09:00
parent 378b015360
commit a481556d31
No known key found for this signature in database
GPG Key ID: E4A36F6D37931A8B
3 changed files with 87 additions and 73 deletions

View File

@ -134,17 +134,7 @@ PHP_MINFO_FUNCTION(bcmath)
Convert to bc_num detecting scale */
static zend_result php_str2num(bc_num *num, char *str)
{
char *p;
if (!(p = strchr(str, '.'))) {
if (!bc_str2num(num, str, 0)) {
return FAILURE;
}
return SUCCESS;
}
if (!bc_str2num(num, str, strlen(p+1))) {
if (!bc_str2num(num, str, 0, true)) {
return FAILURE;
}
@ -624,12 +614,12 @@ PHP_FUNCTION(bccomp)
bc_init_num(&first);
bc_init_num(&second);
if (!bc_str2num(&first, ZSTR_VAL(left), scale)) {
if (!bc_str2num(&first, ZSTR_VAL(left), scale, false)) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}
if (!bc_str2num(&second, ZSTR_VAL(right), scale)) {
if (!bc_str2num(&second, ZSTR_VAL(right), scale, false)) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}

View File

@ -39,16 +39,16 @@ typedef enum {PLUS, MINUS} sign;
typedef struct bc_struct *bc_num;
typedef struct bc_struct {
sign n_sign;
size_t n_len; /* The number of digits before the decimal point. */
size_t n_scale; /* The number of digits after the decimal point. */
int n_refs; /* The number of pointers to this number. */
char *n_ptr; /* The pointer to the actual storage.
If NULL, n_value points to the inside of another number
(bc_multiply...) and should not be "freed." */
char *n_value; /* The number. Not zero char terminated.
May not point to the same place as n_ptr as
in the case of leading zeros generated. */
int n_refs; /* The number of pointers to this number. */
sign n_sign;
} bc_struct;
#ifdef HAVE_CONFIG_H
@ -97,7 +97,7 @@ bc_num bc_copy_num(bc_num num);
void bc_init_num(bc_num *num);
bool bc_str2num(bc_num *num, char *str, size_t scale);
bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale);
zend_string *bc_num2str_ex(bc_num num, size_t scale);

View File

@ -35,20 +35,19 @@
/* Convert strings to bc numbers. Base 10 only.*/
bool bc_str2num(bc_num *num, char *str, size_t scale)
bool bc_str2num(bc_num *num, char *str, size_t scale, bool auto_scale)
{
size_t digits = 0;
size_t strscale = 0;
char *ptr, *nptr;
size_t trailing_zeros = 0;
size_t str_scale = 0;
char *ptr = str;
char *fractional_ptr = NULL;
char *fractional_end = NULL;
bool zero_int = false;
/* Prepare num. */
bc_free_num (num);
/* Check for valid number and count digits. */
ptr = str;
if ((*ptr == '+') || (*ptr == '-')) {
/* Skip Sign */
ptr++;
@ -57,77 +56,102 @@ bool bc_str2num(bc_num *num, char *str, size_t scale)
while (*ptr == '0') {
ptr++;
}
const char *integer_ptr = ptr;
/* digits before the decimal point */
while (*ptr >= '0' && *ptr <= '9') {
ptr++;
digits++;
}
/* decimal point */
if (*ptr == '.') {
ptr++;
}
/* digits after the decimal point */
while (*ptr >= '0' && *ptr <= '9') {
if (*ptr == '0') {
trailing_zeros++;
} else {
trailing_zeros = 0;
}
ptr++;
strscale++;
char *decimal_point = (*ptr == '.') ? ptr : NULL;
/* If a non-digit and non-decimal-point indicator is in the string, i.e. an invalid character */
if (!decimal_point && *ptr != '\0') {
goto fail;
}
if (trailing_zeros > 0) {
/* Trailing zeros should not take part in the computation of the overall scale, as it is pointless. */
strscale = strscale - trailing_zeros;
/* search and validate fractional end if exists */
if (decimal_point) {
/* search */
fractional_ptr = fractional_end = decimal_point + 1;
if (*fractional_ptr == '\0') {
goto after_fractional;
}
/* validate */
while (*fractional_ptr >= '0' && *fractional_ptr <= '9') {
fractional_end = *fractional_ptr != '0' ? fractional_ptr + 1 : fractional_end;
fractional_ptr++;
}
if (*fractional_ptr != '\0') {
/* invalid num */
goto fail;
}
/* Move the pointer to the beginning of the fraction. */
fractional_ptr = decimal_point + 1;
/* Calculate the length of the fraction excluding trailing zero. */
str_scale = fractional_end - fractional_ptr;
/*
* If set the scale manually and it is smaller than the automatically calculated scale,
* adjust it to match the manual setting.
*/
if (str_scale > scale && !auto_scale) {
fractional_end -= str_scale - scale;
str_scale = scale;
}
}
if ((*ptr != '\0') || (digits + strscale == 0)) {
*num = bc_copy_num(BCG(_zero_));
return *ptr == '\0';
after_fractional:
if (digits + str_scale == 0) {
goto zero;
}
/* Adjust numbers and allocate storage and initialize fields. */
strscale = MIN(strscale, scale);
if (digits == 0) {
zero_int = true;
digits = 1;
}
*num = bc_new_num (digits, strscale);
*num = bc_new_num (digits, str_scale);
(*num)->n_sign = *str == '-' ? MINUS : PLUS;
char *nptr = (*num)->n_value;
/* Build the whole number. */
ptr = str;
if (*ptr == '-') {
(*num)->n_sign = MINUS;
ptr++;
} else {
(*num)->n_sign = PLUS;
if (*ptr == '+') ptr++;
}
/* Skip leading zeros. */
while (*ptr == '0') {
ptr++;
}
nptr = (*num)->n_value;
if (zero_int) {
*nptr++ = 0;
digits = 0;
}
for (; digits > 0; digits--) {
*nptr++ = CH_VAL(*ptr++);
}
/* Build the fractional part. */
if (strscale > 0) {
/* skip the decimal point! */
ptr++;
for (; strscale > 0; strscale--) {
*nptr++ = CH_VAL(*ptr++);
nptr++;
/*
* If zero_int is true and the str_scale is 0, there is an early return,
* so here str_scale is always greater than 0.
*/
while (fractional_ptr < fractional_end) {
*nptr = CH_VAL(*fractional_ptr);
nptr++;
fractional_ptr++;
}
} else {
const char *integer_end = integer_ptr + digits;
while (integer_ptr < integer_end) {
*nptr = CH_VAL(*integer_ptr);
nptr++;
integer_ptr++;
}
if (str_scale > 0) {
while (fractional_ptr < fractional_end) {
*nptr = CH_VAL(*fractional_ptr);
nptr++;
fractional_ptr++;
}
}
}
if (bc_is_zero(*num)) {
(*num)->n_sign = PLUS;
}
return true;
zero:
*num = bc_copy_num(BCG(_zero_));
return true;
fail:
*num = bc_copy_num(BCG(_zero_));
return false;
}