cseiler Tue Dec 2 16:27:15 2008 UTC Added files: (Branch: PHP_5_3) /php-src/ext/standard/tests/math round_large_exp.phpt round_modes.phpt round_prerounding.phpt
Modified files: /php-src/ext/standard basic_functions.c config.m4 math.c php_math.h Log: - MFH: Implemented http://wiki.php.net/rfc/rounding
http://cvs.php.net/viewvc.cgi/php-src/ext/standard/basic_functions.c?r1=1.725.2.31.2.64.2.77&r2=1.725.2.31.2.64.2.78&diff_format=u Index: php-src/ext/standard/basic_functions.c diff -u php-src/ext/standard/basic_functions.c:1.725.2.31.2.64.2.77 php-src/ext/standard/basic_functions.c:1.725.2.31.2.64.2.78 --- php-src/ext/standard/basic_functions.c:1.725.2.31.2.64.2.77 Sat Nov 29 00:44:33 2008 +++ php-src/ext/standard/basic_functions.c Tue Dec 2 16:27:14 2008 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: basic_functions.c,v 1.725.2.31.2.64.2.77 2008/11/29 00:44:33 stas Exp $ */ +/* $Id: basic_functions.c,v 1.725.2.31.2.64.2.78 2008/12/02 16:27:14 cseiler Exp $ */ #include "php.h" #include "php_streams.h" @@ -1632,6 +1632,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_round, 0, 0, 1) ZEND_ARG_INFO(0, number) ZEND_ARG_INFO(0, precision) + ZEND_ARG_INFO(0, mode) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_sin, 0) @@ -3571,6 +3572,11 @@ REGISTER_DOUBLE_CONSTANT("INF", php_get_inf(), CONST_CS | CONST_PERSISTENT); REGISTER_DOUBLE_CONSTANT("NAN", php_get_nan(), CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_ROUND_HALF_UP", PHP_ROUND_HALF_UP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_ROUND_HALF_DOWN", PHP_ROUND_HALF_DOWN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_ROUND_HALF_EVEN", PHP_ROUND_HALF_EVEN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PHP_ROUND_HALF_ODD", PHP_ROUND_HALF_ODD, CONST_CS | CONST_PERSISTENT); + #if ENABLE_TEST_CLASS test_class_startup(); #endif http://cvs.php.net/viewvc.cgi/php-src/ext/standard/config.m4?r1=1.80.2.3.2.3.2.6&r2=1.80.2.3.2.3.2.7&diff_format=u Index: php-src/ext/standard/config.m4 diff -u php-src/ext/standard/config.m4:1.80.2.3.2.3.2.6 php-src/ext/standard/config.m4:1.80.2.3.2.3.2.7 --- php-src/ext/standard/config.m4:1.80.2.3.2.3.2.6 Mon Aug 25 13:42:54 2008 +++ php-src/ext/standard/config.m4 Tue Dec 2 16:27:15 2008 @@ -1,4 +1,4 @@ -dnl $Id: config.m4,v 1.80.2.3.2.3.2.6 2008/08/25 13:42:54 jani Exp $ -*- autoconf -*- +dnl $Id: config.m4,v 1.80.2.3.2.3.2.7 2008/12/02 16:27:15 cseiler Exp $ -*- autoconf -*- divert(3)dnl @@ -223,31 +223,6 @@ divert(5)dnl dnl -dnl round fuzz -dnl -AC_MSG_CHECKING([whether rounding works as expected]) -AC_TRY_RUN([ -#include <math.h> - /* keep this out-of-line to prevent use of gcc inline floor() */ - double somefn(double n) { - return floor(n*pow(10,2) + 0.5); - } - int main() { - return somefn(0.045)/10.0 != 0.5; - } -],[ - PHP_ROUND_FUZZ=0.5 - AC_MSG_RESULT(yes) -],[ - PHP_ROUND_FUZZ=0.50000000001 - AC_MSG_RESULT(no) -],[ - PHP_ROUND_FUZZ=0.50000000001 - AC_MSG_RESULT(cross compile) -]) -AC_DEFINE_UNQUOTED(PHP_ROUND_FUZZ, $PHP_ROUND_FUZZ, [ see #24142 ]) - -dnl dnl Check if there is a support means of creating a new process dnl and defining which handles it receives dnl http://cvs.php.net/viewvc.cgi/php-src/ext/standard/math.c?r1=1.131.2.2.2.6.2.10&r2=1.131.2.2.2.6.2.11&diff_format=u Index: php-src/ext/standard/math.c diff -u php-src/ext/standard/math.c:1.131.2.2.2.6.2.10 php-src/ext/standard/math.c:1.131.2.2.2.6.2.11 --- php-src/ext/standard/math.c:1.131.2.2.2.6.2.10 Wed Oct 29 20:03:34 2008 +++ php-src/ext/standard/math.c Tue Dec 2 16:27:15 2008 @@ -19,70 +19,186 @@ +----------------------------------------------------------------------+ */ -/* $Id: math.c,v 1.131.2.2.2.6.2.10 2008/10/29 20:03:34 iliaa Exp $ */ +/* $Id: math.c,v 1.131.2.2.2.6.2.11 2008/12/02 16:27:15 cseiler Exp $ */ #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 */ /* - * Pertains to some of the code found in the php_round() function - * Ref: http://www.freebsd.org/cgi/query-pr.cgi?pr=59797 - * - * Copyright (c) 2003, Steven G. Kargl - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice unmodified, this list of conditions, and the following - * disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * 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 */ -static double php_round(double val, int places) { - double t; - double f = pow(10.0, (double) places); - double x = val * f; - - if (zend_isinf(x) || zend_isnan(x)) { - return val; +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); + } } - if (x >= 0.0) { - t = ceil(x); - if ((t - x) > 0.50000000001) { - t -= 1.0; + /* 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 { - t = ceil(-x); - if ((t + x) > 0.50000000001) { - t -= 1.0; + /* 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; } - t = -t; } - x = t / f; - return !zend_isnan(x) ? x : t; + ZEND_FLOAT_RETURN(tmp_value); } +/* }}} */ /* {{{ php_asinh */ @@ -210,20 +326,21 @@ } /* }}} */ -/* {{{ proto float round(float number [, int precision]) +/* {{{ proto float round(float number [, int precision [, int mode]]) 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|l", &value, &precision) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|ll", &value, &precision, &mode) == FAILURE) { return; } - if (ZEND_NUM_ARGS() == 2) { + if (ZEND_NUM_ARGS() >= 2) { places = (int) precision; } convert_scalar_to_number_ex(value); @@ -238,7 +355,7 @@ case IS_DOUBLE: return_val = (Z_TYPE_PP(value) == IS_LONG) ? (double)Z_LVAL_PP(value) : Z_DVAL_PP(value); - return_val = php_round(return_val, places); + return_val = _php_math_round(return_val, places, mode); RETURN_DOUBLE(return_val); break; @@ -975,7 +1092,7 @@ } dec = MAX(0, dec); - d = php_round(d, dec); + d = _php_math_round(d, dec, PHP_ROUND_HALF_UP); tmplen = spprintf(&tmpbuf, 0, "%.*F", dec, d); http://cvs.php.net/viewvc.cgi/php-src/ext/standard/php_math.h?r1=1.28.2.2.2.3.2.2&r2=1.28.2.2.2.3.2.3&diff_format=u Index: php-src/ext/standard/php_math.h diff -u php-src/ext/standard/php_math.h:1.28.2.2.2.3.2.2 php-src/ext/standard/php_math.h:1.28.2.2.2.3.2.3 --- php-src/ext/standard/php_math.h:1.28.2.2.2.3.2.2 Mon May 5 07:29:41 2008 +++ php-src/ext/standard/php_math.h Tue Dec 2 16:27:15 2008 @@ -17,7 +17,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: php_math.h,v 1.28.2.2.2.3.2.2 2008/05/05 07:29:41 kalle Exp $ */ +/* $Id: php_math.h,v 1.28.2.2.2.3.2.3 2008/12/02 16:27:15 cseiler Exp $ */ #ifndef PHP_MATH_H #define PHP_MATH_H @@ -152,4 +152,21 @@ #define M_SQRT3 1.73205080756887729352 /* sqrt(3) */ #endif +/* Define rounding modes (all are round-to-nearest) */ +#ifndef PHP_ROUND_HALF_UP +#define PHP_ROUND_HALF_UP 0x01 /* Arithmetic rounding, up == away from zero */ +#endif + +#ifndef PHP_ROUND_HALF_DOWN +#define PHP_ROUND_HALF_DOWN 0x02 /* Down == towards zero */ +#endif + +#ifndef PHP_ROUND_HALF_EVEN +#define PHP_ROUND_HALF_EVEN 0x03 /* Banker's rounding */ +#endif + +#ifndef PHP_ROUND_HALF_ODD +#define PHP_ROUND_HALF_ODD 0x04 +#endif + #endif /* PHP_MATH_H */ http://cvs.php.net/viewvc.cgi/php-src/ext/standard/tests/math/round_large_exp.phpt?view=markup&rev=1.1 Index: php-src/ext/standard/tests/math/round_large_exp.phpt +++ php-src/ext/standard/tests/math/round_large_exp.phpt --TEST-- round() works correctly for large exponents --FILE-- <?php var_dump (2e-22 == round (2e-22, 22, PHP_ROUND_HALF_UP)); var_dump (1e-22 == round (1e-22, 22, PHP_ROUND_HALF_UP)); var_dump (2e-23 == round (2e-23, 23, PHP_ROUND_HALF_UP)); var_dump (1e-23 == round (1e-23, 23, PHP_ROUND_HALF_UP)); var_dump (2e-24 == round (2e-24, 24, PHP_ROUND_HALF_UP)); var_dump (1e-24 == round (1e-24, 24, PHP_ROUND_HALF_UP)); var_dump (2e22 == round (2e22, -22, PHP_ROUND_HALF_UP)); var_dump (1e22 == round (1e22, -22, PHP_ROUND_HALF_UP)); var_dump (2e23 == round (2e23, -23, PHP_ROUND_HALF_UP)); var_dump (1e23 == round (1e23, -23, PHP_ROUND_HALF_UP)); var_dump (2e24 == round (2e24, -24, PHP_ROUND_HALF_UP)); var_dump (1e24 == round (1e24, -24, PHP_ROUND_HALF_UP)); ?> --EXPECT-- bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) bool(true) http://cvs.php.net/viewvc.cgi/php-src/ext/standard/tests/math/round_modes.phpt?view=markup&rev=1.1 Index: php-src/ext/standard/tests/math/round_modes.phpt +++ php-src/ext/standard/tests/math/round_modes.phpt --TEST-- round() with different rounding modes --FILE-- <?php var_dump (round (2.5, 0, PHP_ROUND_HALF_UP)); var_dump (round (2.5, 0, PHP_ROUND_HALF_DOWN)); var_dump (round (2.5, 0, PHP_ROUND_HALF_EVEN)); var_dump (round (2.5, 0, PHP_ROUND_HALF_ODD)); var_dump (round (-2.5, 0, PHP_ROUND_HALF_UP)); var_dump (round (-2.5, 0, PHP_ROUND_HALF_DOWN)); var_dump (round (-2.5, 0, PHP_ROUND_HALF_EVEN)); var_dump (round (-2.5, 0, PHP_ROUND_HALF_ODD)); var_dump (round (3.5, 0, PHP_ROUND_HALF_UP)); var_dump (round (3.5, 0, PHP_ROUND_HALF_DOWN)); var_dump (round (3.5, 0, PHP_ROUND_HALF_EVEN)); var_dump (round (3.5, 0, PHP_ROUND_HALF_ODD)); var_dump (round (-3.5, 0, PHP_ROUND_HALF_UP)); var_dump (round (-3.5, 0, PHP_ROUND_HALF_DOWN)); var_dump (round (-3.5, 0, PHP_ROUND_HALF_EVEN)); var_dump (round (-3.5, 0, PHP_ROUND_HALF_ODD)); ?> --EXPECT-- float(3) float(2) float(2) float(3) float(-3) float(-2) float(-2) float(-3) float(4) float(3) float(4) float(3) float(-4) float(-3) float(-4) float(-3) http://cvs.php.net/viewvc.cgi/php-src/ext/standard/tests/math/round_prerounding.phpt?view=markup&rev=1.1 Index: php-src/ext/standard/tests/math/round_prerounding.phpt +++ php-src/ext/standard/tests/math/round_prerounding.phpt --TEST-- round() prerounds results to precision --INI-- precision=14 --FILE-- <?php var_dump (round (0.285, 2, PHP_ROUND_HALF_UP)); ?> --EXPECT-- float(0.29)
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php