Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 47 additions & 18 deletions ext/standard/math.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,27 +93,56 @@ 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 tmp_value;
double integral, fractional;

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;
}
fractional = fabs(modf(value, &integral));

switch (mode) {
case PHP_ROUND_HALF_UP:
if (fractional >= 0.5) {
return integral + copysign(1.0, integral);
}

return integral;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang is able to compile this branchless as per: https://godbolt.org/z/5cb8W1416, which is likely a good thing.


case PHP_ROUND_HALF_DOWN:
if (fractional > 0.5) {
return integral + copysign(1.0, integral);
}

return integral;

case PHP_ROUND_HALF_EVEN:
if (fractional > 0.5) {
return integral + copysign(1.0, integral);
}

if (fractional == 0.5) {
bool even = !fmod(integral, 2.0);

if (!even) {
return integral + copysign(1.0, integral);
}
}

return integral;
case PHP_ROUND_HALF_ODD:
if (fractional > 0.5) {
return integral + copysign(1.0, integral);
}

if (fractional == 0.5) {
bool even = !fmod(integral, 2.0);

if (even) {
return integral + copysign(1.0, integral);
}
}

return integral;
}

return tmp_value;
ZEND_UNREACHABLE();
}
/* }}} */

Expand Down
27 changes: 27 additions & 0 deletions ext/standard/tests/math/round_gh12143_1.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
GH-12143: Test rounding of 0.49999999999999994.
--FILE--
<?php
foreach ([
0.49999999999999994,
-0.49999999999999994,
] as $number) {
foreach ([
'PHP_ROUND_HALF_UP',
'PHP_ROUND_HALF_DOWN',
'PHP_ROUND_HALF_EVEN',
'PHP_ROUND_HALF_ODD',
] as $mode) {
printf("%-20s: %+.17g -> %+.17g\n", $mode, $number, round($number, 0, constant($mode)));
}
}
?>
--EXPECT--
PHP_ROUND_HALF_UP : +0.49999999999999994 -> +0
PHP_ROUND_HALF_DOWN : +0.49999999999999994 -> +0
PHP_ROUND_HALF_EVEN : +0.49999999999999994 -> +0
PHP_ROUND_HALF_ODD : +0.49999999999999994 -> +0
PHP_ROUND_HALF_UP : -0.49999999999999994 -> -0
PHP_ROUND_HALF_DOWN : -0.49999999999999994 -> -0
PHP_ROUND_HALF_EVEN : -0.49999999999999994 -> -0
PHP_ROUND_HALF_ODD : -0.49999999999999994 -> -0
27 changes: 27 additions & 0 deletions ext/standard/tests/math/round_gh12143_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
GH-12143: Test rounding of 0.50000000000000011.
--FILE--
<?php
foreach ([
0.50000000000000011,
-0.50000000000000011,
] as $number) {
foreach ([
'PHP_ROUND_HALF_UP',
'PHP_ROUND_HALF_DOWN',
'PHP_ROUND_HALF_EVEN',
'PHP_ROUND_HALF_ODD',
] as $mode) {
printf("%-20s: %+.17g -> %+.17g\n", $mode, $number, round($number, 0, constant($mode)));
}
}
?>
--EXPECT--
PHP_ROUND_HALF_UP : +0.50000000000000011 -> +1
PHP_ROUND_HALF_DOWN : +0.50000000000000011 -> +1
PHP_ROUND_HALF_EVEN : +0.50000000000000011 -> +1
PHP_ROUND_HALF_ODD : +0.50000000000000011 -> +1
PHP_ROUND_HALF_UP : -0.50000000000000011 -> -1
PHP_ROUND_HALF_DOWN : -0.50000000000000011 -> -1
PHP_ROUND_HALF_EVEN : -0.50000000000000011 -> -1
PHP_ROUND_HALF_ODD : -0.50000000000000011 -> -1
27 changes: 27 additions & 0 deletions ext/standard/tests/math/round_gh12143_3.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
GH-12143: Test rounding of 0.5.
--FILE--
<?php
foreach ([
0.5,
-0.5,
] as $number) {
foreach ([
'PHP_ROUND_HALF_UP',
'PHP_ROUND_HALF_DOWN',
'PHP_ROUND_HALF_EVEN',
'PHP_ROUND_HALF_ODD',
] as $mode) {
printf("%-20s: %+.17g -> %+.17g\n", $mode, $number, round($number, 0, constant($mode)));
}
}
?>
--EXPECT--
PHP_ROUND_HALF_UP : +0.5 -> +1
PHP_ROUND_HALF_DOWN : +0.5 -> +0
PHP_ROUND_HALF_EVEN : +0.5 -> +0
PHP_ROUND_HALF_ODD : +0.5 -> +1
PHP_ROUND_HALF_UP : -0.5 -> -1
PHP_ROUND_HALF_DOWN : -0.5 -> -0
PHP_ROUND_HALF_EVEN : -0.5 -> -0
PHP_ROUND_HALF_ODD : -0.5 -> -1
27 changes: 27 additions & 0 deletions ext/standard/tests/math/round_gh12143_4.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
GH-12143: Test rounding of 1.5.
--FILE--
<?php
foreach ([
1.5,
-1.5,
] as $number) {
foreach ([
'PHP_ROUND_HALF_UP',
'PHP_ROUND_HALF_DOWN',
'PHP_ROUND_HALF_EVEN',
'PHP_ROUND_HALF_ODD',
] as $mode) {
printf("%-20s: %+.17g -> %+.17g\n", $mode, $number, round($number, 0, constant($mode)));
}
}
?>
--EXPECT--
PHP_ROUND_HALF_UP : +1.5 -> +2
PHP_ROUND_HALF_DOWN : +1.5 -> +1
PHP_ROUND_HALF_EVEN : +1.5 -> +2
PHP_ROUND_HALF_ODD : +1.5 -> +1
PHP_ROUND_HALF_UP : -1.5 -> -2
PHP_ROUND_HALF_DOWN : -1.5 -> -1
PHP_ROUND_HALF_EVEN : -1.5 -> -2
PHP_ROUND_HALF_ODD : -1.5 -> -1