Commit 8184e9efec2 for php
commit 8184e9efec27e6c76698e7c67a0e108793402912
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Fri Sep 25 04:55:07 2026 -0400
ext/bcmath: Clear the sign of BcMath\Number results that truncate to zero
A bc_num that is zero must carry PLUS, since bc_compare() orders by sign
first. The bc_divide() fast paths for a divisor of +/-1 or a power of ten,
bc_raise() with a positive exponent, and the Number add/sub/mul helpers
truncate to the requested scale and keep the operand sign, so
(new BcMath\Number('-0.001'))->div(1, 0) compares less than 0, is not
equal to 0, and makes sqrt() throw.
Closes GH-23967
diff --git a/NEWS b/NEWS
index dbb54c4cbaa..a011c225aa6 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.4.27
+- BCMath:
+ . Fixed BcMath\Number results that truncate to zero keeping a negative sign
+ and comparing less than zero. (Ilia Alshanetsky)
+
- CLI
. Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
request activation). (matyhtf)
diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c
index e54e5bfac77..4bdd8a23423 100644
--- a/ext/bcmath/bcmath.c
+++ b/ext/bcmath/bcmath.c
@@ -1048,6 +1048,9 @@ static zend_always_inline void bcmath_number_add_internal(
}
*ret = bc_add(n1, n2, *scale);
(*ret)->n_scale = MIN(*scale, (*ret)->n_scale);
+ if (bc_is_zero(*ret)) {
+ (*ret)->n_sign = PLUS;
+ }
bc_rm_trailing_zeros(*ret);
}
@@ -1060,6 +1063,9 @@ static zend_always_inline void bcmath_number_sub_internal(
}
*ret = bc_sub(n1, n2, *scale);
(*ret)->n_scale = MIN(*scale, (*ret)->n_scale);
+ if (bc_is_zero(*ret)) {
+ (*ret)->n_sign = PLUS;
+ }
bc_rm_trailing_zeros(*ret);
}
@@ -1076,6 +1082,9 @@ static zend_always_inline zend_result bcmath_number_mul_internal(
}
*ret = bc_multiply(n1, n2, *scale);
(*ret)->n_scale = MIN(*scale, (*ret)->n_scale);
+ if (bc_is_zero(*ret)) {
+ (*ret)->n_sign = PLUS;
+ }
bc_rm_trailing_zeros(*ret);
return SUCCESS;
}
diff --git a/ext/bcmath/libbcmath/src/div.c b/ext/bcmath/libbcmath/src/div.c
index ce9ae1e1dd7..a45ffdb7757 100644
--- a/ext/bcmath/libbcmath/src/div.c
+++ b/ext/bcmath/libbcmath/src/div.c
@@ -349,8 +349,12 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale)
*quot = bc_new_num_nonzeroed(numerator->n_len, quot_scale);
char *qptr = (*quot)->n_value;
memcpy(qptr, numerator->n_value, numerator->n_len + quot_scale);
- (*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS;
_bc_rm_leading_zeros(*quot);
+ if (bc_is_zero(*quot)) {
+ (*quot)->n_sign = PLUS;
+ } else {
+ (*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS;
+ }
return true;
}
@@ -475,7 +479,11 @@ bool bc_divide(bc_num numerator, bc_num divisor, bc_num *quot, size_t scale)
for (size_t i = 0; i < numerator_bottom_extension; i++) {
*qptr++ = 0;
}
- (*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS;
+ if (bc_is_zero(*quot)) {
+ (*quot)->n_sign = PLUS;
+ } else {
+ (*quot)->n_sign = numerator->n_sign == divisor->n_sign ? PLUS : MINUS;
+ }
return true;
}
diff --git a/ext/bcmath/libbcmath/src/raise.c b/ext/bcmath/libbcmath/src/raise.c
index 1e283864694..efb30f24ffc 100644
--- a/ext/bcmath/libbcmath/src/raise.c
+++ b/ext/bcmath/libbcmath/src/raise.c
@@ -102,6 +102,9 @@ bool bc_raise(bc_num base, long exponent, bc_num *result, size_t scale) {
bc_free_num (result);
*result = temp;
(*result)->n_scale = MIN(scale, (*result)->n_scale);
+ if (bc_is_zero(*result)) {
+ (*result)->n_sign = PLUS;
+ }
}
bc_free_num (&power);
return true;
diff --git a/ext/bcmath/tests/number/methods/calc_methods_zero_result_sign.phpt b/ext/bcmath/tests/number/methods/calc_methods_zero_result_sign.phpt
new file mode 100644
index 00000000000..1871f0e3ca2
--- /dev/null
+++ b/ext/bcmath/tests/number/methods/calc_methods_zero_result_sign.phpt
@@ -0,0 +1,35 @@
+--TEST--
+BcMath\Number calc methods return an unsigned zero when the result truncates to zero
+--EXTENSIONS--
+bcmath
+--FILE--
+<?php
+$cases = [
+ ['div', '-0.001', '1', 0],
+ ['div', '0.001', '-1', 0],
+ ['div', '-0.001', '10', 2],
+ ['div', '-0.001', '0.1', 1],
+ ['add', '-0.001', '-0.001', 0],
+ ['sub', '-0.001', '0.001', 0],
+ ['mul', '-0.001', '1', 0],
+ ['pow', '-0.1', 3, 2],
+];
+
+foreach ($cases as [$method, $num, $arg, $scale]) {
+ $ret = (new BcMath\Number($num))->$method($arg, $scale);
+ echo "{$num} {$method} {$arg}: {$ret} ", $ret <=> 0, ' ', var_export($ret == 0, true), "\n";
+}
+
+[$quot, $rem] = (new BcMath\Number('-0.001'))->divmod('1', 0);
+echo "-0.001 divmod 1: {$quot} ", $quot <=> 0, ' ', var_export($quot == 0, true), "\n";
+?>
+--EXPECT--
+-0.001 div 1: 0 0 true
+0.001 div -1: 0 0 true
+-0.001 div 10: 0.00 0 true
+-0.001 div 0.1: 0.0 0 true
+-0.001 add -0.001: 0 0 true
+-0.001 sub 0.001: 0 0 true
+-0.001 mul 1: 0 0 true
+-0.1 pow 3: 0.00 0 true
+-0.001 divmod 1: 0 0 true