From a8b422c58b52e589e73c420dceafe12264a415fa Mon Sep 17 00:00:00 2001 From: SakiTakamachi Date: Fri, 22 Sep 2023 18:05:18 +0900 Subject: [PATCH 01/10] optimized round() processing --- ext/standard/math.c | 118 ++++-------------- ext/standard/tests/math/bug24142.phpt | 74 ++++++++--- ...round_gh12143_adjustment_result_digit.phpt | 75 +++++++++++ 3 files changed, 160 insertions(+), 107 deletions(-) create mode 100644 ext/standard/tests/math/round_gh12143_adjustment_result_digit.phpt diff --git a/ext/standard/math.c b/ext/standard/math.c index 4ee5f9482909..cbc811a3d9da 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -30,50 +30,6 @@ #include "basic_functions.h" -/* {{{ php_intlog10abs - Returns floor(log10(fabs(val))), uses fast binary search */ -static inline int php_intlog10abs(double value) { - value = fabs(value); - - if (value < 1e-8 || value > 1e22) { - return (int)floor(log10(value)); - } else { - /* Do a binary search with 5 steps */ - int result = 15; - 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}; - - 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) { @@ -92,8 +48,9 @@ static inline double php_intpow10(int 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) { - double integral, fractional; +static inline double php_round_helper(double adjusted_value, double value, double coefficient, int mode) { + double integral, fractional, edge_case; + double value_abs = fabs(value); /* Split the input value into the integral and fractional part. * @@ -101,11 +58,16 @@ static inline double php_round_helper(double value, int mode) { * the absolute value of the fractional part (which will not result * in branches in the assembly) to make the following cases simpler. */ - fractional = fabs(modf(value, &integral)); + fractional = fabs(modf(adjusted_value, &integral)); + if (fabs(adjusted_value) >= value_abs) { + edge_case = fabs((integral + copysign(0.5, integral)) / coefficient); + } else { + edge_case = fabs((integral + copysign(0.5, integral)) * coefficient); + } switch (mode) { case PHP_ROUND_HALF_UP: - if (fractional >= 0.5) { + if (value_abs >= edge_case) { /* We must increase the magnitude of the integral part * (rounding up / towards infinity). copysign(1.0, integral) * will either result in 1.0 or -1.0 depending on the sign @@ -120,7 +82,7 @@ static inline double php_round_helper(double value, int mode) { return integral; case PHP_ROUND_HALF_DOWN: - if (fractional > 0.5) { + if (value_abs > edge_case) { return integral + copysign(1.0, integral); } @@ -151,11 +113,9 @@ static inline double php_round_helper(double value, int mode) { return integral; case PHP_ROUND_HALF_EVEN: - if (fractional > 0.5) { + if (value_abs > edge_case) { return integral + copysign(1.0, integral); - } - - if (UNEXPECTED(fractional == 0.5)) { + } else if (value_abs == edge_case) { bool even = !fmod(integral, 2.0); /* If the integral part is not even we can make it even @@ -169,11 +129,9 @@ static inline double php_round_helper(double value, int mode) { return integral; case PHP_ROUND_HALF_ODD: - if (fractional > 0.5) { + if (value_abs > edge_case) { return integral + copysign(1.0, integral); - } - - if (UNEXPECTED(fractional == 0.5)) { + } else if (value_abs == edge_case) { bool even = !fmod(integral, 2.0); if (even) { @@ -196,56 +154,30 @@ 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; if (!zend_finite(value) || value == 0.0) { return value; } places = places < INT_MIN+1 ? INT_MIN+1 : places; - 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 - 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) { - 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; - 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; + /* adjust the value */ + if (places >= 0) { + tmp_value = value * f1; } 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) { - return value; - } + tmp_value = value / f1; + } + /* This value is beyond our precision, so rounding it is pointless */ + if (fabs(tmp_value) >= 1e15) { + return value; } /* round the temp value */ - tmp_value = php_round_helper(tmp_value, mode); + tmp_value = php_round_helper(tmp_value, value, f1, mode); /* see if it makes sense to use simple division to round the value */ if (abs(places) < 23) { 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_adjustment_result_digit.phpt b/ext/standard/tests/math/round_gh12143_adjustment_result_digit.phpt new file mode 100644 index 000000000000..62853aeb7948 --- /dev/null +++ b/ext/standard/tests/math/round_gh12143_adjustment_result_digit.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) From 0faa198e5254d4719d27705771b10a8ee09e341c Mon Sep 17 00:00:00 2001 From: SakiTakamachi Date: Fri, 22 Sep 2023 18:13:08 +0900 Subject: [PATCH 02/10] optimized round() processing --- ext/standard/math.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index cbc811a3d9da..ba49d362ea53 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -49,16 +49,10 @@ static inline double php_intpow10(int power) { /* {{{ php_round_helper Actually performs the rounding of a value to integer in a certain mode */ static inline double php_round_helper(double adjusted_value, double value, double coefficient, int mode) { - double integral, fractional, edge_case; + double integral = adjusted_value >= 0.0 ? floor(adjusted_value) : ceil(adjusted_value); double value_abs = fabs(value); + double edge_case; - /* Split the input value into the integral and fractional part. - * - * Both parts will have the same sign as the input value. We take - * the absolute value of the fractional part (which will not result - * in branches in the assembly) to make the following cases simpler. - */ - fractional = fabs(modf(adjusted_value, &integral)); if (fabs(adjusted_value) >= value_abs) { edge_case = fabs((integral + copysign(0.5, integral)) / coefficient); } else { From bf3daaf723e596c522ce0db5ed8deb650775fbd0 Mon Sep 17 00:00:00 2001 From: SakiTakamachi Date: Fri, 22 Sep 2023 18:21:27 +0900 Subject: [PATCH 03/10] rename test --- ...ment_result_digit.phpt => round_gh12143_optimize_round.phpt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename ext/standard/tests/math/{round_gh12143_adjustment_result_digit.phpt => round_gh12143_optimize_round.phpt} (98%) diff --git a/ext/standard/tests/math/round_gh12143_adjustment_result_digit.phpt b/ext/standard/tests/math/round_gh12143_optimize_round.phpt similarity index 98% rename from ext/standard/tests/math/round_gh12143_adjustment_result_digit.phpt rename to ext/standard/tests/math/round_gh12143_optimize_round.phpt index 62853aeb7948..91a193757ec6 100644 --- a/ext/standard/tests/math/round_gh12143_adjustment_result_digit.phpt +++ b/ext/standard/tests/math/round_gh12143_optimize_round.phpt @@ -1,5 +1,5 @@ --TEST-- -Fix GH-12143: Remove pre round +Fix GH-12143: Optimize round --FILE-- Date: Sat, 9 Dec 2023 23:54:28 +0900 Subject: [PATCH 04/10] use UNEXPECTED --- ext/standard/math.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index ba49d362ea53..6584bef1e1e2 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -109,7 +109,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl case PHP_ROUND_HALF_EVEN: if (value_abs > edge_case) { return integral + copysign(1.0, integral); - } else if (value_abs == edge_case) { + } else if (UNEXPECTED(value_abs == edge_case)) { bool even = !fmod(integral, 2.0); /* If the integral part is not even we can make it even @@ -125,7 +125,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl case PHP_ROUND_HALF_ODD: if (value_abs > edge_case) { return integral + copysign(1.0, integral); - } else if (value_abs == edge_case) { + } else if (UNEXPECTED(value_abs == edge_case)) { bool even = !fmod(integral, 2.0); if (even) { From 5bfcbe5d68fe64c82309209b5f50a8b8c00f9950 Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Mon, 29 Jan 2024 22:06:57 +0900 Subject: [PATCH 05/10] Unify variable names such as f1 to "exponent" --- ext/standard/math.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index 6584bef1e1e2..9b35503c0d50 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -48,15 +48,15 @@ static inline double php_intpow10(int power) { /* {{{ php_round_helper Actually performs the rounding of a value to integer in a certain mode */ -static inline double php_round_helper(double adjusted_value, double value, double coefficient, int mode) { +static inline double php_round_helper(double adjusted_value, double value, double exponent, int mode) { double integral = adjusted_value >= 0.0 ? floor(adjusted_value) : ceil(adjusted_value); double value_abs = fabs(value); double edge_case; if (fabs(adjusted_value) >= value_abs) { - edge_case = fabs((integral + copysign(0.5, integral)) / coefficient); + edge_case = fabs((integral + copysign(0.5, integral)) / exponent); } else { - edge_case = fabs((integral + copysign(0.5, integral)) * coefficient); + edge_case = fabs((integral + copysign(0.5, integral)) * exponent); } switch (mode) { @@ -148,7 +148,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl * 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; + double exponent; double tmp_value; if (!zend_finite(value) || value == 0.0) { @@ -157,13 +157,13 @@ PHPAPI double _php_math_round(double value, int places, int mode) { places = places < INT_MIN+1 ? INT_MIN+1 : places; - f1 = php_intpow10(abs(places)); + exponent = php_intpow10(abs(places)); /* adjust the value */ if (places >= 0) { - tmp_value = value * f1; + tmp_value = value * exponent; } else { - tmp_value = value / f1; + tmp_value = value / exponent; } /* This value is beyond our precision, so rounding it is pointless */ if (fabs(tmp_value) >= 1e15) { @@ -171,14 +171,14 @@ PHPAPI double _php_math_round(double value, int places, int mode) { } /* round the temp value */ - tmp_value = php_round_helper(tmp_value, value, f1, mode); + tmp_value = php_round_helper(tmp_value, value, exponent, 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; + tmp_value = tmp_value / exponent; } else { - tmp_value = tmp_value * f1; + tmp_value = tmp_value * exponent; } } else { /* Simple division can't be used since that will cause wrong results. From 1b41af8967436658781f17490d6c34157837958f Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Mon, 29 Jan 2024 22:37:16 +0900 Subject: [PATCH 06/10] Compatible with new modes --- ext/standard/math.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index 9b35503c0d50..bef8a008e8c9 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -46,6 +46,22 @@ static inline double php_intpow10(int power) { } /* }}} */ +#define PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent) do {\ + if (fabs(adjusted_value) >= value_abs) {\ + edge_case = fabs((integral + copysign(0.5, integral)) / exponent);\ + } else {\ + edge_case = fabs((integral + copysign(0.5, integral)) * exponent);\ + }\ +} while(0); + +#define PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent) do {\ + if (fabs(adjusted_value) >= value_abs) {\ + edge_case = fabs((integral) / exponent);\ + } else {\ + edge_case = fabs((integral) * exponent);\ + }\ +} while(0); + /* {{{ php_round_helper Actually performs the rounding of a value to integer in a certain mode */ static inline double php_round_helper(double adjusted_value, double value, double exponent, int mode) { @@ -53,14 +69,9 @@ static inline double php_round_helper(double adjusted_value, double value, doubl double value_abs = fabs(value); double edge_case; - if (fabs(adjusted_value) >= value_abs) { - edge_case = fabs((integral + copysign(0.5, integral)) / exponent); - } else { - edge_case = fabs((integral + copysign(0.5, integral)) * exponent); - } - switch (mode) { case PHP_ROUND_HALF_UP: + PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); if (value_abs >= edge_case) { /* We must increase the magnitude of the integral part * (rounding up / towards infinity). copysign(1.0, integral) @@ -76,6 +87,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_HALF_DOWN: + PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } @@ -83,14 +95,16 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_CEILING: - if (value > 0.0 && fractional > 0.0) { + PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + if (value > 0.0 && value_abs > edge_case) { return integral + 1.0; } return integral; case PHP_ROUND_FLOOR: - if (value < 0.0 && fractional > 0.0) { + PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + if (value < 0.0 && value_abs > edge_case) { return integral - 1.0; } @@ -100,13 +114,15 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_AWAY_FROM_ZERO: - if (fractional > 0.0) { + PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + if (value_abs > edge_case) { return integral + copysign(1.0, integral); } return integral; case PHP_ROUND_HALF_EVEN: + PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } else if (UNEXPECTED(value_abs == edge_case)) { @@ -123,6 +139,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_HALF_ODD: + PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } else if (UNEXPECTED(value_abs == edge_case)) { From b9d4f3c3e47f14c9fd3bf0a04bdccdadb87b8f4f Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Wed, 31 Jan 2024 18:44:40 +0900 Subject: [PATCH 07/10] Changes the CPU rounding mode only during a specific process. --- ext/standard/math.c | 78 +++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index bef8a008e8c9..6d7a37fc33b4 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "basic_functions.h" @@ -46,32 +47,30 @@ static inline double php_intpow10(int power) { } /* }}} */ -#define PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent) do {\ - if (fabs(adjusted_value) >= value_abs) {\ - edge_case = fabs((integral + copysign(0.5, integral)) / exponent);\ - } else {\ - edge_case = fabs((integral + copysign(0.5, integral)) * exponent);\ - }\ -} while(0); - -#define PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent) do {\ - if (fabs(adjusted_value) >= value_abs) {\ - edge_case = fabs((integral) / exponent);\ - } else {\ - edge_case = fabs((integral) * exponent);\ - }\ -} while(0); +#define PHP_ROUND_BASIC_EDGE_CASE() do {\ + if (places > 0) {\ + edge_case = fabs((integral + copysign(0.5, integral)) / exponent);\ + } else {\ + edge_case = fabs((integral + copysign(0.5, integral)) * exponent);\ + }\ + } while (0) +#define PHP_ROUND_ZERO_EDGE_CASE() do {\ + if (places > 0) {\ + edge_case = fabs((integral) / exponent);\ + } else {\ + edge_case = fabs((integral) * exponent);\ + }\ + } while (0) /* {{{ php_round_helper - Actually performs the rounding of a value to integer in a certain mode */ -static inline double php_round_helper(double adjusted_value, double value, double exponent, int mode) { - double integral = adjusted_value >= 0.0 ? floor(adjusted_value) : ceil(adjusted_value); + Actually performs the rounding of a value to integer in a certain mode */ +static inline double php_round_helper(double integral, double value, double exponent, int places, int mode) { double value_abs = fabs(value); double edge_case; switch (mode) { case PHP_ROUND_HALF_UP: - PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + PHP_ROUND_BASIC_EDGE_CASE(); if (value_abs >= edge_case) { /* We must increase the magnitude of the integral part * (rounding up / towards infinity). copysign(1.0, integral) @@ -87,7 +86,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_HALF_DOWN: - PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + PHP_ROUND_BASIC_EDGE_CASE(); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } @@ -95,7 +94,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_CEILING: - PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + PHP_ROUND_ZERO_EDGE_CASE(); if (value > 0.0 && value_abs > edge_case) { return integral + 1.0; } @@ -103,7 +102,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_FLOOR: - PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + PHP_ROUND_ZERO_EDGE_CASE(); if (value < 0.0 && value_abs > edge_case) { return integral - 1.0; } @@ -114,7 +113,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_AWAY_FROM_ZERO: - PHP_ROUND_GET_ZERO_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + PHP_ROUND_ZERO_EDGE_CASE(); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } @@ -122,7 +121,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_HALF_EVEN: - PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + PHP_ROUND_BASIC_EDGE_CASE(); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } else if (UNEXPECTED(value_abs == edge_case)) { @@ -139,7 +138,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl return integral; case PHP_ROUND_HALF_ODD: - PHP_ROUND_GET_EDGE_CASE(adjusted_value, value_abs, integral, exponent); + PHP_ROUND_BASIC_EDGE_CASE(); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } else if (UNEXPECTED(value_abs == edge_case)) { @@ -167,6 +166,7 @@ static inline double php_round_helper(double adjusted_value, double value, doubl PHPAPI double _php_math_round(double value, int places, int mode) { double exponent; double tmp_value; + int cpu_round_mode; if (!zend_finite(value) || value == 0.0) { return value; @@ -176,19 +176,36 @@ PHPAPI double _php_math_round(double value, int places, int mode) { exponent = php_intpow10(abs(places)); - /* adjust the value */ - if (places >= 0) { - tmp_value = value * exponent; + /** + * When extracting the integer part, the result may be incorrect as a decimal + * number due to floating point errors. + * e.g. + * 0.285 * 10000000000 => 2849999999.9999995 + * floor(0.285 * 10000000000) => 2849999999 + * + * Therefore, change the CPU rounding mode to away from 0 only from + * fegetround to fesetround. + * e.g. + * 0.285 * 10000000000 => 2850000000.0 + * floor(0.285 * 10000000000) => 2850000000 + */ + cpu_round_mode = fegetround(); + if (value >= 0.0) { + fesetround(FE_UPWARD); + tmp_value = floor(places > 0 ? value * exponent : value / exponent); } else { - tmp_value = value / exponent; + fesetround(FE_DOWNWARD); + tmp_value = ceil(places > 0 ? value * exponent : value / exponent); } + fesetround(cpu_round_mode); + /* This value is beyond our precision, so rounding it is pointless */ if (fabs(tmp_value) >= 1e15) { return value; } /* round the temp value */ - tmp_value = php_round_helper(tmp_value, value, exponent, mode); + tmp_value = php_round_helper(tmp_value, value, exponent, places, mode); /* see if it makes sense to use simple division to round the value */ if (abs(places) < 23) { @@ -215,7 +232,6 @@ PHPAPI double _php_math_round(double value, int places, int mode) { tmp_value = value; } } - return tmp_value; } /* }}} */ From 1a3625405d6bec62fb5b8d197d0c0855139ef116 Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Wed, 31 Jan 2024 20:16:14 +0900 Subject: [PATCH 08/10] [skip ci] NEWS/UPGRADING --- NEWS | 1 + UPGRADING | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 06df7dd8fb44..d922ca6bcd89 100644 --- a/NEWS +++ b/NEWS @@ -149,6 +149,7 @@ Standard: the precision is not lost. (Marc Bennewitz) . Add support for 4 new rounding modes to the round() function. (Jorg Sowa) . debug_zval_dump() now indicates whether an array is packed. (Max Semenik) + . Fix GH-12143 (Optimize round). (SakiTakamachi) XML: . Added XML_OPTION_PARSE_HUGE parser option. (nielsdos) diff --git a/UPGRADING b/UPGRADING index 752724bfe1ce..f352780472e9 100644 --- a/UPGRADING +++ b/UPGRADING @@ -343,6 +343,11 @@ PDO_SQLITE: RFC: https://wiki.php.net/rfc/new_rounding_modes_to_round_function . debug_zval_dump() now indicates whether an array is packed. + . Fixed a bug caused by "pre-rounding" of the round() function. Previously, using + "pre-rounding" to treat a value like 0.285 (actually 0.28499999999999998) as a + decimal number and round it to 0.29. However, "pre-rounding" incorrectly rounds + certain numbers, so this fix removes "pre-rounding" and changes the way numbers + are compared, so that the values ​​are correctly rounded as decimal numbers. ======================================== 6. New Functions From e7e4725efe5697aaabde7a7de57095e6a112f8d0 Mon Sep 17 00:00:00 2001 From: Saki Takamachi Date: Thu, 1 Feb 2024 09:00:21 +0900 Subject: [PATCH 09/10] use zend_always_inline --- ext/standard/math.c | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index 6d7a37fc33b4..13ab808ef050 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -47,30 +47,29 @@ static inline double php_intpow10(int power) { } /* }}} */ -#define PHP_ROUND_BASIC_EDGE_CASE() do {\ - if (places > 0) {\ - edge_case = fabs((integral + copysign(0.5, integral)) / exponent);\ - } else {\ - edge_case = fabs((integral + copysign(0.5, integral)) * exponent);\ - }\ - } while (0) -#define PHP_ROUND_ZERO_EDGE_CASE() do {\ - if (places > 0) {\ - edge_case = fabs((integral) / exponent);\ - } else {\ - edge_case = fabs((integral) * exponent);\ - }\ - } while (0) +static zend_always_inline double php_round_get_basic_edge_case(const double integral, const double exponent, const int places) +{ + return (places > 0) + ? fabs((integral + copysign(0.5, integral)) / exponent) + : fabs((integral + copysign(0.5, integral)) * exponent); +} + +static zend_always_inline double php_round_get_zero_edge_case(const double integral, const double exponent, const int places) +{ + return (places > 0) + ? fabs((integral) / exponent) + : fabs((integral) * exponent); +} /* {{{ php_round_helper Actually performs the rounding of a value to integer in a certain mode */ -static inline double php_round_helper(double integral, double value, double exponent, int places, int mode) { +static inline double php_round_helper(double integral, const double value, const double exponent, const int places, const int mode) { double value_abs = fabs(value); double edge_case; switch (mode) { case PHP_ROUND_HALF_UP: - PHP_ROUND_BASIC_EDGE_CASE(); + edge_case = php_round_get_basic_edge_case(integral, exponent, places); if (value_abs >= edge_case) { /* We must increase the magnitude of the integral part * (rounding up / towards infinity). copysign(1.0, integral) @@ -86,7 +85,7 @@ static inline double php_round_helper(double integral, double value, double expo return integral; case PHP_ROUND_HALF_DOWN: - PHP_ROUND_BASIC_EDGE_CASE(); + edge_case = php_round_get_basic_edge_case(integral, exponent, places); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } @@ -94,7 +93,7 @@ static inline double php_round_helper(double integral, double value, double expo return integral; case PHP_ROUND_CEILING: - PHP_ROUND_ZERO_EDGE_CASE(); + edge_case = php_round_get_zero_edge_case(integral, exponent, places); if (value > 0.0 && value_abs > edge_case) { return integral + 1.0; } @@ -102,7 +101,7 @@ static inline double php_round_helper(double integral, double value, double expo return integral; case PHP_ROUND_FLOOR: - PHP_ROUND_ZERO_EDGE_CASE(); + edge_case = php_round_get_zero_edge_case(integral, exponent, places); if (value < 0.0 && value_abs > edge_case) { return integral - 1.0; } @@ -113,7 +112,7 @@ static inline double php_round_helper(double integral, double value, double expo return integral; case PHP_ROUND_AWAY_FROM_ZERO: - PHP_ROUND_ZERO_EDGE_CASE(); + edge_case = php_round_get_zero_edge_case(integral, exponent, places); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } @@ -121,7 +120,7 @@ static inline double php_round_helper(double integral, double value, double expo return integral; case PHP_ROUND_HALF_EVEN: - PHP_ROUND_BASIC_EDGE_CASE(); + edge_case = php_round_get_basic_edge_case(integral, exponent, places); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } else if (UNEXPECTED(value_abs == edge_case)) { @@ -138,7 +137,7 @@ static inline double php_round_helper(double integral, double value, double expo return integral; case PHP_ROUND_HALF_ODD: - PHP_ROUND_BASIC_EDGE_CASE(); + edge_case = php_round_get_basic_edge_case(integral, exponent, places); if (value_abs > edge_case) { return integral + copysign(1.0, integral); } else if (UNEXPECTED(value_abs == edge_case)) { From 0c64e30d86dc5854f0fb488ce0eefa023c9a1661 Mon Sep 17 00:00:00 2001 From: Saki Takamachi <34942839+SakiTakamachi@users.noreply.github.com> Date: Fri, 2 Feb 2024 01:54:43 +0900 Subject: [PATCH 10/10] Remove unnecessary "const" --- ext/standard/math.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/standard/math.c b/ext/standard/math.c index 13ab808ef050..4de45852c349 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -47,14 +47,14 @@ static inline double php_intpow10(int power) { } /* }}} */ -static zend_always_inline double php_round_get_basic_edge_case(const double integral, const double exponent, const int places) +static zend_always_inline double php_round_get_basic_edge_case(double integral, double exponent, int places) { return (places > 0) ? fabs((integral + copysign(0.5, integral)) / exponent) : fabs((integral + copysign(0.5, integral)) * exponent); } -static zend_always_inline double php_round_get_zero_edge_case(const double integral, const double exponent, const int places) +static zend_always_inline double php_round_get_zero_edge_case(double integral, double exponent, int places) { return (places > 0) ? fabs((integral) / exponent) @@ -63,7 +63,7 @@ static zend_always_inline double php_round_get_zero_edge_case(const double integ /* {{{ php_round_helper Actually performs the rounding of a value to integer in a certain mode */ -static inline double php_round_helper(double integral, const double value, const double exponent, const int places, const int mode) { +static inline double php_round_helper(double integral, double value, double exponent, int places, int mode) { double value_abs = fabs(value); double edge_case;