diff --git a/ext/standard/math.c b/ext/standard/math.c index 05f2252e304e..e61b80bf22e6 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -169,41 +169,42 @@ static inline double php_round_helper(double value, int mode) { * 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) { - double f1, f2; + double f1; double tmp_value; - int precision_places; + int value_places; if (!zend_finite(value) || value == 0.0) { return value; } places = places < INT_MIN+1 ? INT_MIN+1 : places; - precision_places = 14 - php_intlog10abs(value); + value_places = -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 - 15 < places) { - int64_t use_precision = precision_places < INT_MIN+1 ? INT_MIN+1 : precision_places; - - f2 = php_intpow10(abs((int)use_precision)); - if (use_precision >= 0) { + /* + * In order to minimize the influence of errors specific to floating point numbers, + * digits are adjusted to ensure accuracy. Digits adjustment is performed only if + * the location specifies a range that can be represented by floating point numbers. + */ + if (value_places + 16 > places && value_places <= places) { + int precision_places = value_places + 14; + double 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); - use_precision = places - precision_places; + int use_precision = places - precision_places; use_precision = use_precision < INT_MIN+1 ? INT_MIN+1 : use_precision; - /* now correctly move the decimal point */ - f2 = php_intpow10(abs((int)use_precision)); - /* because places < precision_places */ - tmp_value = tmp_value / f2; + f2 = php_intpow10(abs(use_precision)); + + if (use_precision >= 0) { + tmp_value = tmp_value * f2; + } else { + tmp_value = tmp_value / f2; + } } else { /* adjust the value */ if (places >= 0) { diff --git a/ext/standard/tests/math/bug24142.phpt b/ext/standard/tests/math/bug24142.phpt index 947deaebd669..739ac25421dc 100644 --- a/ext/standard/tests/math/bug24142.phpt +++ b/ext/standard/tests/math/bug24142.phpt @@ -2,19 +2,65 @@ Bug #24142 (round() problems) --FILE-- ".round($v, 2)."\n"; - $v += 0.01; -} +echo "round(0.005, 2)\n"; +var_dump(round(0.005, 2)); +echo "\n"; + +echo "round(0.015, 2)\n"; +var_dump(round(0.015, 2)); +echo "\n"; + +echo "round(0.025, 2)\n"; +var_dump(round(0.025, 2)); +echo "\n"; + +echo "round(0.035, 2)\n"; +var_dump(round(0.035, 2)); +echo "\n"; + +echo "round(0.045, 2)\n"; +var_dump(round(0.045, 2)); +echo "\n"; + +echo "round(0.055, 2)\n"; +var_dump(round(0.055, 2)); +echo "\n"; + +echo "round(0.065, 2)\n"; +var_dump(round(0.065, 2)); +echo "\n"; + +echo "round(0.075, 2)\n"; +var_dump(round(0.075, 2)); +echo "\n"; + +echo "round(0.085, 2)\n"; +var_dump(round(0.085, 2)); ?> --EXPECT-- -round(0.005, 2) -> 0.01 -round(0.015, 2) -> 0.02 -round(0.025, 2) -> 0.03 -round(0.035, 2) -> 0.04 -round(0.045, 2) -> 0.05 -round(0.055, 2) -> 0.06 -round(0.065, 2) -> 0.07 -round(0.075, 2) -> 0.08 -round(0.085, 2) -> 0.09 +round(0.005, 2) +float(0.01) + +round(0.015, 2) +float(0.02) + +round(0.025, 2) +float(0.03) + +round(0.035, 2) +float(0.04) + +round(0.045, 2) +float(0.05) + +round(0.055, 2) +float(0.06) + +round(0.065, 2) +float(0.07) + +round(0.075, 2) +float(0.08) + +round(0.085, 2) +float(0.09) diff --git a/ext/standard/tests/math/round_gh12143_remove_pre_round.phpt b/ext/standard/tests/math/round_gh12143_remove_pre_round.phpt new file mode 100644 index 000000000000..62853aeb7948 --- /dev/null +++ b/ext/standard/tests/math/round_gh12143_remove_pre_round.phpt @@ -0,0 +1,75 @@ +--TEST-- +Fix GH-12143: Remove pre round +--FILE-- + +--EXPECT-- +HALF_UP +float(1.7000000000001) +float(-1.7000000000001) +float(123456789012340) +float(-123456789012340) + +HALF_DOWN +float(1.7000000000001) +float(-1.7000000000001) +float(123456789012340) +float(-123456789012340) +float(2) +float(-2) + +HALF_EVEN +float(1.7000000000002) +float(-1.7000000000002) +float(1.7000000000008) +float(-1.7000000000008) +float(12345678901234) +float(-12345678901234) +float(2) +float(-2) + +HALF_ODD +float(1.7000000000003) +float(-1.7000000000003) +float(1.7000000000007) +float(-1.7000000000007) +float(12345678901233) +float(-12345678901233) +float(2) +float(-2)