diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index 7be9d4b01528a..99eedcb427357 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -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; } diff --git a/ext/bcmath/libbcmath/src/bcmath.h b/ext/bcmath/libbcmath/src/bcmath.h index 27800d3425736..3e005e2072cc3 100644 --- a/ext/bcmath/libbcmath/src/bcmath.h +++ b/ext/bcmath/libbcmath/src/bcmath.h @@ -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); diff --git a/ext/bcmath/libbcmath/src/str2num.c b/ext/bcmath/libbcmath/src/str2num.c index cc876285d59c0..2f2f55377e83b 100644 --- a/ext/bcmath/libbcmath/src/str2num.c +++ b/ext/bcmath/libbcmath/src/str2num.c @@ -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++; + 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; } - /* digits after the decimal point */ - while (*ptr >= '0' && *ptr <= '9') { - if (*ptr == '0') { - trailing_zeros++; - } else { - trailing_zeros = 0; + + /* 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; } - ptr++; - strscale++; - } - 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; + /* 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; }