diff options
Diffstat (limited to 'ext/standard/math.c')
-rw-r--r-- | ext/standard/math.c | 199 |
1 files changed, 24 insertions, 175 deletions
diff --git a/ext/standard/math.c b/ext/standard/math.c index 3d76a6ee0d..ac9c206437 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -24,181 +24,31 @@ #include "php.h" #include "php_math.h" #include "zend_multiply.h" -#include "zend_float.h" #include <math.h> #include <float.h> #include <stdlib.h> -/* {{{ php_intlog10abs - Returns floor(log10(fabs(val))), uses fast binary search */ -static inline int php_intlog10abs(double value) { - int result; - value = fabs(value); - - if (value < 1e-8 || value > 1e23) { - result = (int)floor(log10(value)); - } else { - static const double values[] = { - 1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, - 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, - 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; - /* Do a binary search with 5 steps */ - result = 16; - if (value < values[result]) { - result -= 8; - } else { - result += 8; - } - if (value < values[result]) { - result -= 4; - } else { - result += 4; - } - if (value < values[result]) { - result -= 2; - } else { - result += 2; - } - if (value < values[result]) { - result -= 1; - } else { - result += 1; - } - if (value < values[result]) { - result -= 1; - } - result -= 8; - } - return result; -} -/* }}} */ - -/* {{{ php_intpow10 - Returns pow(10.0, (double)power), uses fast lookup table for exact powers */ -static inline double php_intpow10(int power) { - static const double powers[] = { - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, - 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, - 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; - - /* Not in lookup table */ - if (power < 0 || power > 22) { - return pow(10.0, (double)power); - } - return powers[power]; -} -/* }}} */ - -/* {{{ php_round_helper - Actually performs the rounding of a value to integer in a certain mode */ -static inline double php_round_helper(double value, int mode) { - ZEND_FLOAT_DECLARE - double tmp_value; - - ZEND_FLOAT_ENSURE(); - if (value >= 0.0) { - tmp_value = floor(value + 0.5); - if ((mode == PHP_ROUND_HALF_DOWN && value == (-0.5 + tmp_value)) || - (mode == PHP_ROUND_HALF_EVEN && value == (0.5 + 2 * floor(tmp_value/2.0))) || - (mode == PHP_ROUND_HALF_ODD && value == (0.5 + 2 * floor(tmp_value/2.0) - 1.0))) - { - tmp_value = tmp_value - 1.0; - } - } else { - tmp_value = ceil(value - 0.5); - if ((mode == PHP_ROUND_HALF_DOWN && value == (0.5 + tmp_value)) || - (mode == PHP_ROUND_HALF_EVEN && value == (-0.5 + 2 * ceil(tmp_value/2.0))) || - (mode == PHP_ROUND_HALF_ODD && value == (-0.5 + 2 * ceil(tmp_value/2.0) + 1.0))) - { - tmp_value = tmp_value + 1.0; - } - } - - ZEND_FLOAT_RETURN(tmp_value); -} -/* }}} */ - -/* {{{ _php_math_round */ -/* - * Rounds a number to a certain number of decimal places in a certain rounding - * mode. For the specifics of the algorithm, see http://wiki.php.net/rfc/rounding - */ -PHPAPI double _php_math_round(double value, int places, int mode) { - ZEND_FLOAT_DECLARE - double f1, f2; - double tmp_value; - int precision_places; - - ZEND_FLOAT_ENSURE(); - - precision_places = 14 - php_intlog10abs(value); - - f1 = php_intpow10(abs(places)); - - /* If the decimal precision guaranteed by FP arithmetic is higher than - the requested places BUT is small enough to make sure a non-zero value - is returned, pre-round the result to the precision */ - if (precision_places > places && precision_places - places < 15) { - f2 = php_intpow10(abs(precision_places)); - if (precision_places >= 0) { - tmp_value = value * f2; - } else { - tmp_value = value / f2; - } - /* preround the result (tmp_value will always be something * 1e14, - thus never larger than 1e15 here) */ - tmp_value = php_round_helper(tmp_value, mode); - /* now correctly move the decimal point */ - f2 = php_intpow10(abs(places - precision_places)); - /* because places < precision_places */ - tmp_value = tmp_value / f2; - } else { - /* adjust the value */ - if (places >= 0) { - tmp_value = value * f1; - } else { - tmp_value = value / f1; - } - /* This value is beyond our precision, so rounding it is pointless */ - if (fabs(tmp_value) >= 1e15) { - ZEND_FLOAT_RETURN(value); - } - } +#ifndef PHP_ROUND_FUZZ +# ifndef PHP_WIN32 +# define PHP_ROUND_FUZZ 0.50000000001 +# else +# define PHP_ROUND_FUZZ 0.5 +# endif +#endif - /* round the temp value */ - tmp_value = php_round_helper(tmp_value, mode); - - /* see if it makes sense to use simple division to round the value */ - if (abs(places) < 23) { - if (places > 0) { - tmp_value = tmp_value / f1; - } else { - tmp_value = tmp_value * f1; - } - } else { - /* Simple division can't be used since that will cause wrong results. - Instead, the number is converted to a string and back again using - strtod(). strtod() will return the nearest possible FP value for - that string. */ - - /* 40 Bytes should be more than enough for this format string. The - float won't be larger than 1e15 anyway. But just in case, use - snprintf() and make sure the buffer is zero-terminated */ - char buf[40]; - snprintf(buf, 39, "%15fe%d", tmp_value, -places); - buf[39] = '\0'; - tmp_value = zend_strtod(buf, NULL); - /* couldn't convert to string and back */ - if (!zend_finite(tmp_value) || zend_isnan(tmp_value)) { - tmp_value = value; - } - } +#define PHP_ROUND_WITH_FUZZ(val, places) { \ + double tmp_val=val, f = pow(10.0, (double) places); \ + tmp_val *= f; \ + if (tmp_val >= 0.0) { \ + tmp_val = floor(tmp_val + PHP_ROUND_FUZZ); \ + } else { \ + tmp_val = ceil(tmp_val - PHP_ROUND_FUZZ); \ + } \ + tmp_val /= f; \ + val = !zend_isnan(tmp_val) ? tmp_val : val; \ +} \ - ZEND_FLOAT_RETURN(tmp_value); -} -/* }}} */ /* {{{ php_asinh */ @@ -326,21 +176,20 @@ PHP_FUNCTION(floor) } /* }}} */ -/* {{{ proto float round(float number [, int precision [, int mode]]) +/* {{{ proto float round(float number [, int precision]) Returns the number rounded to specified precision */ PHP_FUNCTION(round) { zval **value; int places = 0; long precision = 0; - long mode = PHP_ROUND_HALF_UP; double return_val; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|ll", &value, &precision, &mode) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|l", &value, &precision) == FAILURE) { return; } - if (ZEND_NUM_ARGS() >= 2) { + if (ZEND_NUM_ARGS() == 2) { places = (int) precision; } convert_scalar_to_number_ex(value); @@ -355,7 +204,7 @@ PHP_FUNCTION(round) case IS_DOUBLE: return_val = (Z_TYPE_PP(value) == IS_LONG) ? (double)Z_LVAL_PP(value) : Z_DVAL_PP(value); - return_val = _php_math_round(return_val, places, mode); + PHP_ROUND_WITH_FUZZ(return_val, places); RETURN_DOUBLE(return_val); break; @@ -1092,7 +941,7 @@ PHPAPI char *_php_math_number_format(double d, int dec, char dec_point, char tho } dec = MAX(0, dec); - d = _php_math_round(d, dec, PHP_ROUND_HALF_UP); + PHP_ROUND_WITH_FUZZ(d, dec); tmplen = spprintf(&tmpbuf, 0, "%.*F", dec, d); @@ -1191,7 +1040,7 @@ PHPAPI char *_php_math_number_format(double d, int dec, char dec_point, char tho PHP_FUNCTION(number_format) { double num; - long dec = 0; + long dec; char *thousand_sep = NULL, *dec_point = NULL; char thousand_sep_chr = ',', dec_point_chr = '.'; int thousand_sep_len = 0, dec_point_len = 0; |