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

Reply via email to