diff --git a/ext/standard/math.c b/ext/standard/math.c index 142d473864f75..274da5d820f13 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -51,9 +51,21 @@ static inline double php_intpow10(int power) { 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); + /* Integer + `0.5` can be represented exactly in a double, so in most cases, there is no rounding error at this point. */ + double edge_case = integral + copysign(0.5, integral); + + /* When the calculation precision is as high as `1e16`, adding `0.5` may be lost due to rounding. + * In such cases, the exponent is applied individually before performing the addition. + * This approach is not used all the time in order to avoid introducing rounding errors when adding `0.5`. + * It is only used when the precision is so fine that adding `0.5` would have no meaningful effect. */ + if (UNEXPECTED(integral == edge_case)) { + return (places > 0) + ? fabs((integral / exponent) + (copysign(0.5, integral) / exponent)) + : fabs((integral * exponent) + (copysign(0.5, integral) * exponent)); + } + + /* Returns a value adjusted with the exponent so that it can be compared with the input. */ + return (places > 0) ? fabs(edge_case / exponent) : fabs(edge_case * exponent); } static zend_always_inline double php_round_get_zero_edge_case(double integral, double exponent, int places) diff --git a/ext/standard/tests/math/round_gh12143_expand_rounding_target.phpt b/ext/standard/tests/math/round_gh12143_expand_rounding_target.phpt index b96a7ffe3e3be..0a427975c623e 100644 --- a/ext/standard/tests/math/round_gh12143_expand_rounding_target.phpt +++ b/ext/standard/tests/math/round_gh12143_expand_rounding_target.phpt @@ -13,6 +13,9 @@ $testCases = [ [-12345678901234565, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], 'PHP_ROUND_HALF_DOWN' => [ [0.12345678901234565, 16], @@ -21,6 +24,9 @@ $testCases = [ [-12345678901234565, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], 'PHP_ROUND_HALF_EVEN' => [ [0.12345678901234565, 16], @@ -29,6 +35,9 @@ $testCases = [ [-12345678901234565, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], 'PHP_ROUND_HALF_ODD' => [ [0.12345678901234565, 16], @@ -37,6 +46,9 @@ $testCases = [ [-12345678901234565, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], 'RoundingMode::AwayFromZero' => [ [0.12345678901234560, 16], @@ -45,6 +57,9 @@ $testCases = [ [-12345678901234567, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], 'RoundingMode::TowardsZero' => [ [0.12345678901234566, 16], @@ -53,6 +68,9 @@ $testCases = [ [-12345678901234565, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], 'RoundingMode::PositiveInfinity' => [ [0.12345678901234560, 16], @@ -61,6 +79,9 @@ $testCases = [ [-12345678901234564, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], 'RoundingMode::NegativeInfinity' => [ [0.12345678901234560, 16], @@ -69,6 +90,9 @@ $testCases = [ [-12345678901234564, -1], [4503599627370495.5, 0], [-4503599627370495.5, 0], + [5.0, 15], + [5000000000000000.0, 0], + [5000000000000000.5, 0], ], ]; @@ -88,6 +112,9 @@ float(12345678901234570) float(-12345678901234570) float(4503599627370496) float(-4503599627370496) +float(5) +float(5000000000000001) +float(5000000000000001) ========== PHP_ROUND_HALF_DOWN ========== float(0.1234567890123456) @@ -96,6 +123,9 @@ float(12345678901234560) float(-12345678901234560) float(4503599627370495) float(-4503599627370495) +float(5) +float(5000000000000000) +float(5000000000000000) ========== PHP_ROUND_HALF_EVEN ========== float(0.1234567890123456) @@ -104,6 +134,9 @@ float(12345678901234560) float(-12345678901234560) float(4503599627370496) float(-4503599627370496) +float(5) +float(5000000000000000) +float(5000000000000000) ========== PHP_ROUND_HALF_ODD ========== float(0.1234567890123457) @@ -112,6 +145,9 @@ float(12345678901234570) float(-12345678901234570) float(4503599627370495) float(-4503599627370495) +float(5) +float(5000000000000001) +float(5000000000000001) ========== RoundingMode::AwayFromZero ========== float(0.1234567890123456) @@ -120,6 +156,9 @@ float(12345678901234570) float(-12345678901234570) float(4503599627370496) float(-4503599627370496) +float(5) +float(5000000000000000) +float(5000000000000000) ========== RoundingMode::TowardsZero ========== float(0.1234567890123456) @@ -128,6 +167,9 @@ float(12345678901234560) float(-12345678901234560) float(4503599627370495) float(-4503599627370495) +float(5) +float(5000000000000000) +float(5000000000000000) ========== RoundingMode::PositiveInfinity ========== float(0.1234567890123456) @@ -136,6 +178,9 @@ float(12345678901234570) float(-12345678901234560) float(4503599627370496) float(-4503599627370495) +float(5) +float(5000000000000000) +float(5000000000000000) ========== RoundingMode::NegativeInfinity ========== float(0.1234567890123456) @@ -144,3 +189,6 @@ float(12345678901234560) float(-12345678901234570) float(4503599627370495) float(-4503599627370496) +float(5) +float(5000000000000000) +float(5000000000000000)