Skip to content

Commit f6db576

Browse files
[RFC] ext/bcmath: Added bcdivmod (php#15740)
RFC: https://wiki.php.net/rfc/add_bcdivmod_to_bcmath Added bcdivmod() function and added divmod() method to BcMath\Number class.
1 parent 2b90acb commit f6db576

File tree

11 files changed

+681
-1
lines changed

11 files changed

+681
-1
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ PHP NEWS
55
- BcMath:
66
. bcpow() performance improvement. (Jorg Sowa)
77
. ext/bcmath: Check for scale overflow. (SakiTakamachi)
8+
. [RFC] ext/bcmath: Added bcdivmod. (SakiTakamachi)
89

910
- Debugging:
1011
. Fixed bug GH-15923 (GDB: Python Exception <class 'TypeError'>:

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,8 @@ PHP 8.4 UPGRADE NOTES
780780
- BCMath:
781781
. Added bcfloor(), bcceil(), bcround().
782782
RFC: https://wiki.php.net/rfc/adding_bcround_bcfloor_bcceil_to_bcmath
783+
. Added bcdivmod().
784+
RFC: https://wiki.php.net/rfc/add_bcdivmod_to_bcmath
783785

784786
- DOM:
785787
. Added DOMNode::compareDocumentPosition().

Zend/Optimizer/zend_func_infos.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ static const func_info_t func_infos[] = {
2424
F1("bcmul", MAY_BE_STRING),
2525
F1("bcdiv", MAY_BE_STRING),
2626
F1("bcmod", MAY_BE_STRING),
27+
F1("bcdivmod", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
2728
F1("bcpowmod", MAY_BE_STRING),
2829
F1("bcpow", MAY_BE_STRING),
2930
F1("bcsqrt", MAY_BE_STRING),

ext/bcmath/bcmath.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,61 @@ PHP_FUNCTION(bcmod)
434434
}
435435
/* }}} */
436436

437+
PHP_FUNCTION(bcdivmod)
438+
{
439+
zend_string *left, *right;
440+
zend_long scale_param;
441+
bool scale_param_is_null = 1;
442+
bc_num first = NULL, second = NULL, quot = NULL, rem = NULL;
443+
int scale = BCG(bc_precision);
444+
445+
ZEND_PARSE_PARAMETERS_START(2, 3)
446+
Z_PARAM_STR(left)
447+
Z_PARAM_STR(right)
448+
Z_PARAM_OPTIONAL
449+
Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
450+
ZEND_PARSE_PARAMETERS_END();
451+
452+
if (scale_param_is_null) {
453+
scale = BCG(bc_precision);
454+
} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
455+
RETURN_THROWS();
456+
} else {
457+
scale = (int) scale_param;
458+
}
459+
460+
BC_ARENA_SETUP;
461+
462+
if (php_str2num(&first, left) == FAILURE) {
463+
zend_argument_value_error(1, "is not well-formed");
464+
goto cleanup;
465+
}
466+
467+
if (php_str2num(&second, right) == FAILURE) {
468+
zend_argument_value_error(2, "is not well-formed");
469+
goto cleanup;
470+
}
471+
472+
if (!bc_divmod(first, second, &quot, &rem, scale)) {
473+
zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
474+
goto cleanup;
475+
}
476+
477+
zval z_quot, z_rem;
478+
ZVAL_STR(&z_quot, bc_num2str_ex(quot, 0));
479+
ZVAL_STR(&z_rem, bc_num2str_ex(rem, scale));
480+
481+
RETVAL_ARR(zend_new_pair(&z_quot, &z_rem));
482+
483+
cleanup: {
484+
bc_free_num(&first);
485+
bc_free_num(&second);
486+
bc_free_num(&quot);
487+
bc_free_num(&rem);
488+
BC_ARENA_TEARDOWN;
489+
};
490+
}
491+
437492
/* {{{ Returns the value of an arbitrary precision number raised to the power of another reduced by a modulus */
438493
PHP_FUNCTION(bcpowmod)
439494
{
@@ -1452,6 +1507,65 @@ PHP_METHOD(BcMath_Number, pow)
14521507
bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_POW);
14531508
}
14541509

1510+
PHP_METHOD(BcMath_Number, divmod)
1511+
{
1512+
zend_object *num_obj = NULL;
1513+
zend_string *num_str = NULL;
1514+
zend_long num_lval = 0;
1515+
zend_long scale_lval = 0;
1516+
bool scale_is_null = true;
1517+
1518+
ZEND_PARSE_PARAMETERS_START(1, 2)
1519+
BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(num_obj, bcmath_number_ce, num_str, num_lval);
1520+
Z_PARAM_OPTIONAL
1521+
Z_PARAM_LONG_OR_NULL(scale_lval, scale_is_null);
1522+
ZEND_PARSE_PARAMETERS_END();
1523+
1524+
bc_num num = NULL;
1525+
size_t num_full_scale;
1526+
if (bc_num_from_obj_or_str_or_long_with_err(&num, &num_full_scale, num_obj, num_str, num_lval, 1) == FAILURE) {
1527+
goto fail;
1528+
}
1529+
if (bcmath_check_scale(scale_lval, 2) == FAILURE) {
1530+
goto fail;
1531+
}
1532+
1533+
bc_num quot = NULL;
1534+
bc_num rem = NULL;
1535+
size_t scale = scale_lval;
1536+
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);
1537+
1538+
if (scale_is_null) {
1539+
scale = MAX(intern->scale, num_full_scale);
1540+
}
1541+
1542+
if (!bc_divmod(intern->num, num, &quot, &rem, scale)) {
1543+
zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
1544+
goto fail;
1545+
}
1546+
bc_rm_trailing_zeros(quot);
1547+
bc_rm_trailing_zeros(rem);
1548+
1549+
if (num_obj == NULL) {
1550+
bc_free_num(&num);
1551+
}
1552+
1553+
bcmath_number_obj_t *quot_intern = bcmath_number_new_obj(quot, 0);
1554+
bcmath_number_obj_t *rem_intern = bcmath_number_new_obj(rem, scale);
1555+
1556+
zval z_quot, z_rem;
1557+
ZVAL_OBJ(&z_quot, &quot_intern->std);
1558+
ZVAL_OBJ(&z_rem, &rem_intern->std);
1559+
1560+
RETURN_ARR(zend_new_pair(&z_quot, &z_rem));
1561+
1562+
fail:
1563+
if (num_obj == NULL) {
1564+
bc_free_num(&num);
1565+
}
1566+
RETURN_THROWS();
1567+
}
1568+
14551569
PHP_METHOD(BcMath_Number, powmod)
14561570
{
14571571
zend_object *exponent_obj = NULL;

ext/bcmath/bcmath.stub.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ function bcdiv(string $num1, string $num2, ?int $scale = null): string {}
1919
/** @refcount 1 */
2020
function bcmod(string $num1, string $num2, ?int $scale = null): string {}
2121

22+
/**
23+
* @return string[]
24+
* @refcount 1
25+
*/
26+
function bcdivmod(string $num1, string $num2, ?int $scale = null): array {}
27+
2228
/** @refcount 1 */
2329
function bcpowmod(string $num, string $exponent, string $modulus, ?int $scale = null): string {}
2430

@@ -64,6 +70,9 @@ public function div(Number|string|int $num, ?int $scale = null): Number {}
6470

6571
public function mod(Number|string|int $num, ?int $scale = null): Number {}
6672

73+
/** @return Number[] */
74+
public function divmod(Number|string|int $num, ?int $scale = null): array {}
75+
6776
public function powmod(Number|string|int $exponent, Number|string|int $modulus, ?int $scale = null): Number {}
6877

6978
public function pow(Number|string|int $exponent, ?int $scale = null): Number {}

ext/bcmath/bcmath_arginfo.h

Lines changed: 16 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/bcmath/tests/bcdivmod.phpt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
bcdivmod() function
3+
--EXTENSIONS--
4+
bcmath
5+
--INI--
6+
bcmath.scale=0
7+
--FILE--
8+
<?php
9+
require(__DIR__ . "/run_bcmath_tests_function.inc");
10+
11+
$dividends = ["15", "-15", "1", "-9", "14.14", "-16.60", "0.15", "-0.01"];
12+
$divisors = array_merge($dividends, [
13+
"15151324141414.412312232141241",
14+
"-132132245132134.1515123765412",
15+
"141241241241241248267654747412",
16+
"-149143276547656984948124912",
17+
"0.1322135476547459213732911312",
18+
"-0.123912932193769965476541321",
19+
]);
20+
21+
$scales = [0, 10];
22+
foreach ($scales as $scale) {
23+
foreach ($dividends as $firstTerm) {
24+
foreach ($divisors as $secondTerm) {
25+
[$quot, $rem] = bcdivmod($firstTerm, $secondTerm, $scale);
26+
$div_ret = bcdiv($firstTerm, $secondTerm, 0);
27+
$mod_ret = bcmod($firstTerm, $secondTerm, $scale);
28+
29+
if (bccomp($quot, $div_ret) !== 0) {
30+
echo "Div result is incorrect.\n";
31+
var_dump($firstTerm, $secondTerm, $scale, $quot, $rem, $div_ret, $mod_ret);
32+
echo "\n";
33+
}
34+
35+
if (bccomp($rem, $mod_ret) !== 0) {
36+
echo "Mod result is incorrect.\n";
37+
var_dump($firstTerm, $secondTerm, $scale, $quot, $rem, $div_ret, $mod_ret);
38+
echo "\n";
39+
}
40+
}
41+
}
42+
}
43+
echo 'done!';
44+
?>
45+
--EXPECT--
46+
done!
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
bcdivmod() function div by zero
3+
--EXTENSIONS--
4+
bcmath
5+
--INI--
6+
bcmath.scale=0
7+
--FILE--
8+
<?php
9+
require(__DIR__ . "/run_bcmath_tests_function.inc");
10+
11+
$dividends = [
12+
"15", "-15", "1", "-9", "14.14", "-16.60", "0.15", "-0.01",
13+
"15151324141414.412312232141241",
14+
"-132132245132134.1515123765412",
15+
"141241241241241248267654747412",
16+
"-149143276547656984948124912",
17+
"0.1322135476547459213732911312",
18+
"-0.123912932193769965476541321",
19+
];
20+
21+
$divisors = [
22+
'0',
23+
'0.00',
24+
];
25+
26+
foreach ($dividends as $firstTerm) {
27+
foreach ($divisors as $secondTerm) {
28+
try {
29+
bcdivmod($firstTerm, $secondTerm);
30+
echo "NG\n";
31+
} catch (Error $e) {
32+
echo $e->getMessage() === 'Division by zero' ? 'OK' :'NG';
33+
echo "\n";
34+
}
35+
}
36+
}
37+
?>
38+
--EXPECT--
39+
OK
40+
OK
41+
OK
42+
OK
43+
OK
44+
OK
45+
OK
46+
OK
47+
OK
48+
OK
49+
OK
50+
OK
51+
OK
52+
OK
53+
OK
54+
OK
55+
OK
56+
OK
57+
OK
58+
OK
59+
OK
60+
OK
61+
OK
62+
OK
63+
OK
64+
OK
65+
OK
66+
OK

0 commit comments

Comments
 (0)