Skip to content

Commit 52ebdfb

Browse files
committed
Correctly round rounding mode with zero edge case (#17065)
Fixes #17064 Closes #17065
1 parent 47b2620 commit 52ebdfb

13 files changed

+84
-15
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ PHP NEWS
66
. Fixed bug GH-17049 (Correctly compare 0 and -0). (Saki Takamachi)
77
. Fixed bug GH-17061 (Now Number::round() does not remove trailing zeros).
88
(Saki Takamachi)
9+
. Fixed bug GH-17064 (Correctly round rounding mode with zero edge case).
10+
(Saki Takamachi)
911

1012
- Core:
1113
. Fixed bug OSS-Fuzz #382922236 (Duplicate dynamic properties in hooked object

ext/bcmath/libbcmath/src/round.c

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,54 @@ void bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
3333
* - If the fractional part ends with zeros, the zeros are omitted and the number of digits in num is reduced.
3434
* Meaning we might end up in the previous case.
3535
*/
36+
37+
/* e.g. value is 0.1 and precision is -3, ret is 0 or 1000 */
3638
if (precision < 0 && num->n_len < (size_t) (-(precision + Z_L(1))) + 1) {
37-
*result = bc_copy_num(BCG(_zero_));
39+
switch (mode) {
40+
case PHP_ROUND_HALF_UP:
41+
case PHP_ROUND_HALF_DOWN:
42+
case PHP_ROUND_HALF_EVEN:
43+
case PHP_ROUND_HALF_ODD:
44+
case PHP_ROUND_TOWARD_ZERO:
45+
*result = bc_copy_num(BCG(_zero_));
46+
return;
47+
48+
case PHP_ROUND_CEILING:
49+
if (num->n_sign == MINUS) {
50+
*result = bc_copy_num(BCG(_zero_));
51+
return;
52+
}
53+
break;
54+
55+
case PHP_ROUND_FLOOR:
56+
if (num->n_sign == PLUS) {
57+
*result = bc_copy_num(BCG(_zero_));
58+
return;
59+
}
60+
break;
61+
62+
case PHP_ROUND_AWAY_FROM_ZERO:
63+
break;
64+
65+
EMPTY_SWITCH_DEFAULT_CASE()
66+
}
67+
68+
if (bc_is_zero(num)) {
69+
*result = bc_copy_num(BCG(_zero_));
70+
return;
71+
}
72+
73+
/* If precision is -3, it becomes 1000. */
74+
if (UNEXPECTED(precision == ZEND_LONG_MIN)) {
75+
*result = bc_new_num((size_t) ZEND_LONG_MAX + 2, 0);
76+
} else {
77+
*result = bc_new_num(-precision + 1, 0);
78+
}
79+
(*result)->n_value[0] = 1;
80+
(*result)->n_sign = num->n_sign;
3881
return;
3982
}
83+
4084
/* Just like bcadd('1', '1', 4) becomes '2.0000', it pads with zeros at the end if necessary. */
4185
if (precision >= 0 && num->n_scale <= precision) {
4286
if (num->n_scale == precision) {
@@ -61,7 +105,7 @@ void bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
61105
* If the result of rounding is carried over, it will be added later, so first set it to 0 here.
62106
*/
63107
if (rounded_len == 0) {
64-
*result = bc_copy_num(BCG(_zero_));
108+
*result = bc_new_num(1, 0);
65109
} else {
66110
*result = bc_new_num(num->n_len, precision > 0 ? precision : 0);
67111
memcpy((*result)->n_value, num->n_value, rounded_len);

ext/bcmath/tests/bcround_away_from_zero.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::AwayFromZero);
2727
[-1.9, 0] => -2
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 1000
32+
[-0.01, -3] => -1000
3033
[50, -2] => 100
3134
[-50, -2] => -100
3235
[1230, -1] => 1230

ext/bcmath/tests/bcround_ceiling.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::PositiveInfinity);
2727
[-1.9, 0] => -1
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 1000
32+
[-0.01, -3] => 0
3033
[50, -2] => 100
3134
[-50, -2] => 0
3235
[1230, -1] => 1230

ext/bcmath/tests/bcround_early_return.phpt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,11 @@ bcmath
66
<?php
77

88
$early_return_cases = [
9-
['123', -4],
10-
['123.123456', -4],
119
['123', 1],
1210
['123.5', 1],
1311
['123.5', 2],
1412
['123.0000000000000000000001', 22],
1513
['123.0000000000000000000001', 23],
16-
['-123', -4],
17-
['-123.123456', -4],
1814
['-123', 1],
1915
['-123.5', 1],
2016
['-123.5', 2],
@@ -54,15 +50,11 @@ foreach (RoundingMode::cases() as $mode) {
5450
}
5551
?>
5652
--EXPECT--
57-
[123, -4] => 0
58-
[123.123456, -4] => 0
5953
[123, 1] => 123.0
6054
[123.5, 1] => 123.5
6155
[123.5, 2] => 123.50
6256
[123.0000000000000000000001, 22] => 123.0000000000000000000001
6357
[123.0000000000000000000001, 23] => 123.00000000000000000000010
64-
[-123, -4] => 0
65-
[-123.123456, -4] => 0
6658
[-123, 1] => -123.0
6759
[-123.5, 1] => -123.5
6860
[-123.5, 2] => -123.50

ext/bcmath/tests/bcround_floor.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::NegativeInfinity);
2727
[-1.9, 0] => -2
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 0
32+
[-0.01, -3] => -1000
3033
[50, -2] => 0
3134
[-50, -2] => -100
3235
[1230, -1] => 1230

ext/bcmath/tests/bcround_half_down.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::HalfTowardsZero);
2727
[-1.9, 0] => -2
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 0
32+
[-0.01, -3] => 0
3033
[50, -2] => 0
3134
[-50, -2] => 0
3235
[1230, -1] => 1230

ext/bcmath/tests/bcround_half_even.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::HalfEven);
2727
[-1.9, 0] => -2
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 0
32+
[-0.01, -3] => 0
3033
[50, -2] => 0
3134
[-50, -2] => 0
3235
[1230, -1] => 1230

ext/bcmath/tests/bcround_half_odd.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::HalfOdd);
2727
[-1.9, 0] => -2
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 0
32+
[-0.01, -3] => 0
3033
[50, -2] => 100
3134
[-50, -2] => -100
3235
[1230, -1] => 1230

ext/bcmath/tests/bcround_half_up.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::HalfAwayFromZero);
2727
[-1.9, 0] => -2
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 0
32+
[-0.01, -3] => 0
3033
[50, -2] => 100
3134
[-50, -2] => -100
3235
[1230, -1] => 1230

ext/bcmath/tests/bcround_test_helper.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ function run_round_test(RoundingMode $mode)
3030
];
3131

3232
$minus_precision_cases = [
33+
['0', -3],
34+
['0.01', -3],
35+
['-0.01', -3],
3336
['50', -2],
3437
['-50', -2],
3538
['1230', -1],

ext/bcmath/tests/bcround_toward_zero.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ run_round_test(RoundingMode::TowardsZero);
2727
[-1.9, 0] => -1
2828

2929
========== minus precision ==========
30+
[0, -3] => 0
31+
[0.01, -3] => 0
32+
[-0.01, -3] => 0
3033
[50, -2] => 0
3134
[-50, -2] => 0
3235
[1230, -1] => 1230

ext/bcmath/tests/number/methods/round.phpt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ bcmath
66
<?php
77
foreach (RoundingMode::cases() as $mode) {
88
foreach ([
9+
'0',
910
'0.1',
1011
'-0.1',
1112
'1.0',
@@ -19,17 +20,20 @@ foreach (RoundingMode::cases() as $mode) {
1920
'2.5',
2021
'-2.5',
2122
] as $number) {
22-
$func_ret = bcround($number, 0, $mode);
23-
$method_ret = (new BcMath\Number($number))->round(0, $mode);
24-
if ($method_ret->compare($func_ret) !== 0) {
25-
echo "Result is incorrect.\n";
26-
var_dump($number, $mode, $func_ret, $method_ret);
23+
foreach ([0, 5, -5] as $scale) {
24+
$func_ret = bcround($number, $scale, $mode);
25+
$method_ret = (new BcMath\Number($number))->round($scale, $mode);
26+
if ($method_ret->compare($func_ret) !== 0) {
27+
echo "Result is incorrect.\n";
28+
var_dump($number, $mode, $func_ret, $method_ret);
29+
}
2730
}
2831
}
2932
}
3033

3134
foreach (RoundingMode::cases() as $mode) {
3235
foreach ([
36+
'0',
3337
'1.2345678',
3438
'-1.2345678',
3539
] as $number) {

0 commit comments

Comments
 (0)