Skip to content

[RFC] ext/bcmath: Added bcdivmod #15740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ PHP NEWS
- BcMath:
. bcpow() performance improvement. (Jorg Sowa)
. ext/bcmath: Check for scale overflow. (SakiTakamachi)
. [RFC] ext/bcmath: Added bcdivmod. (SakiTakamachi)

- Debugging:
. Fixed bug GH-15923 (GDB: Python Exception <class 'TypeError'>:
Expand Down
2 changes: 2 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,8 @@ PHP 8.4 UPGRADE NOTES
- BCMath:
. Added bcfloor(), bcceil(), bcround().
RFC: https://wiki.php.net/rfc/adding_bcround_bcfloor_bcceil_to_bcmath
. Added bcdivmod().
RFC: https://wiki.php.net/rfc/add_bcdivmod_to_bcmath

- DOM:
. Added DOMNode::compareDocumentPosition().
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_func_infos.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ static const func_info_t func_infos[] = {
F1("bcmul", MAY_BE_STRING),
F1("bcdiv", MAY_BE_STRING),
F1("bcmod", MAY_BE_STRING),
F1("bcdivmod", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
F1("bcpowmod", MAY_BE_STRING),
F1("bcpow", MAY_BE_STRING),
F1("bcsqrt", MAY_BE_STRING),
Expand Down
114 changes: 114 additions & 0 deletions ext/bcmath/bcmath.c
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,61 @@ PHP_FUNCTION(bcmod)
}
/* }}} */

PHP_FUNCTION(bcdivmod)
{
zend_string *left, *right;
zend_long scale_param;
bool scale_param_is_null = 1;
bc_num first = NULL, second = NULL, quot = NULL, rem = NULL;
int scale = BCG(bc_precision);

ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STR(left)
Z_PARAM_STR(right)
Z_PARAM_OPTIONAL
Z_PARAM_LONG_OR_NULL(scale_param, scale_param_is_null)
ZEND_PARSE_PARAMETERS_END();

if (scale_param_is_null) {
scale = BCG(bc_precision);
} else if (bcmath_check_scale(scale_param, 3) == FAILURE) {
RETURN_THROWS();
} else {
scale = (int) scale_param;
}

BC_ARENA_SETUP;

if (php_str2num(&first, left) == FAILURE) {
zend_argument_value_error(1, "is not well-formed");
goto cleanup;
}

if (php_str2num(&second, right) == FAILURE) {
zend_argument_value_error(2, "is not well-formed");
goto cleanup;
}

if (!bc_divmod(first, second, &quot, &rem, scale)) {
zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
goto cleanup;
}

zval z_quot, z_rem;
ZVAL_STR(&z_quot, bc_num2str_ex(quot, 0));
ZVAL_STR(&z_rem, bc_num2str_ex(rem, scale));

RETVAL_ARR(zend_new_pair(&z_quot, &z_rem));

cleanup: {
bc_free_num(&first);
bc_free_num(&second);
bc_free_num(&quot);
bc_free_num(&rem);
BC_ARENA_TEARDOWN;
};
}

/* {{{ Returns the value of an arbitrary precision number raised to the power of another reduced by a modulus */
PHP_FUNCTION(bcpowmod)
{
Expand Down Expand Up @@ -1452,6 +1507,65 @@ PHP_METHOD(BcMath_Number, pow)
bcmath_number_calc_method(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_POW);
}

PHP_METHOD(BcMath_Number, divmod)
{
zend_object *num_obj = NULL;
zend_string *num_str = NULL;
zend_long num_lval = 0;
zend_long scale_lval = 0;
bool scale_is_null = true;

ZEND_PARSE_PARAMETERS_START(1, 2)
BCMATH_PARAM_NUMBER_OR_STR_OR_LONG(num_obj, bcmath_number_ce, num_str, num_lval);
Z_PARAM_OPTIONAL
Z_PARAM_LONG_OR_NULL(scale_lval, scale_is_null);
ZEND_PARSE_PARAMETERS_END();

bc_num num = NULL;
size_t num_full_scale;
if (bc_num_from_obj_or_str_or_long_with_err(&num, &num_full_scale, num_obj, num_str, num_lval, 1) == FAILURE) {
goto fail;
}
if (bcmath_check_scale(scale_lval, 2) == FAILURE) {
goto fail;
}

bc_num quot = NULL;
bc_num rem = NULL;
size_t scale = scale_lval;
bcmath_number_obj_t *intern = get_bcmath_number_from_zval(ZEND_THIS);

if (scale_is_null) {
scale = MAX(intern->scale, num_full_scale);
}

if (!bc_divmod(intern->num, num, &quot, &rem, scale)) {
zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Division by zero");
goto fail;
}
bc_rm_trailing_zeros(quot);
bc_rm_trailing_zeros(rem);

if (num_obj == NULL) {
bc_free_num(&num);
}

bcmath_number_obj_t *quot_intern = bcmath_number_new_obj(quot, 0);
bcmath_number_obj_t *rem_intern = bcmath_number_new_obj(rem, scale);

zval z_quot, z_rem;
ZVAL_OBJ(&z_quot, &quot_intern->std);
ZVAL_OBJ(&z_rem, &rem_intern->std);

RETURN_ARR(zend_new_pair(&z_quot, &z_rem));

fail:
if (num_obj == NULL) {
bc_free_num(&num);
}
RETURN_THROWS();
}

PHP_METHOD(BcMath_Number, powmod)
{
zend_object *exponent_obj = NULL;
Expand Down
9 changes: 9 additions & 0 deletions ext/bcmath/bcmath.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ function bcdiv(string $num1, string $num2, ?int $scale = null): string {}
/** @refcount 1 */
function bcmod(string $num1, string $num2, ?int $scale = null): string {}

/**
* @return string[]
* @refcount 1
*/
function bcdivmod(string $num1, string $num2, ?int $scale = null): array {}

/** @refcount 1 */
function bcpowmod(string $num, string $exponent, string $modulus, ?int $scale = null): string {}

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

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

/** @return Number[] */
public function divmod(Number|string|int $num, ?int $scale = null): array {}

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

public function pow(Number|string|int $exponent, ?int $scale = null): Number {}
Expand Down
17 changes: 16 additions & 1 deletion ext/bcmath/bcmath_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions ext/bcmath/tests/bcdivmod.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
bcdivmod() function
--EXTENSIONS--
bcmath
--INI--
bcmath.scale=0
--FILE--
<?php
require(__DIR__ . "/run_bcmath_tests_function.inc");

$dividends = ["15", "-15", "1", "-9", "14.14", "-16.60", "0.15", "-0.01"];
$divisors = array_merge($dividends, [
"15151324141414.412312232141241",
"-132132245132134.1515123765412",
"141241241241241248267654747412",
"-149143276547656984948124912",
"0.1322135476547459213732911312",
"-0.123912932193769965476541321",
]);

$scales = [0, 10];
foreach ($scales as $scale) {
foreach ($dividends as $firstTerm) {
foreach ($divisors as $secondTerm) {
[$quot, $rem] = bcdivmod($firstTerm, $secondTerm, $scale);
$div_ret = bcdiv($firstTerm, $secondTerm, 0);
$mod_ret = bcmod($firstTerm, $secondTerm, $scale);

if (bccomp($quot, $div_ret) !== 0) {
echo "Div result is incorrect.\n";
var_dump($firstTerm, $secondTerm, $scale, $quot, $rem, $div_ret, $mod_ret);
echo "\n";
}

if (bccomp($rem, $mod_ret) !== 0) {
echo "Mod result is incorrect.\n";
var_dump($firstTerm, $secondTerm, $scale, $quot, $rem, $div_ret, $mod_ret);
echo "\n";
}
}
}
}
echo 'done!';
?>
--EXPECT--
done!
66 changes: 66 additions & 0 deletions ext/bcmath/tests/bcdivmod_by_zero.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
--TEST--
bcdivmod() function div by zero
--EXTENSIONS--
bcmath
--INI--
bcmath.scale=0
--FILE--
<?php
require(__DIR__ . "/run_bcmath_tests_function.inc");

$dividends = [
"15", "-15", "1", "-9", "14.14", "-16.60", "0.15", "-0.01",
"15151324141414.412312232141241",
"-132132245132134.1515123765412",
"141241241241241248267654747412",
"-149143276547656984948124912",
"0.1322135476547459213732911312",
"-0.123912932193769965476541321",
];

$divisors = [
'0',
'0.00',
];

foreach ($dividends as $firstTerm) {
foreach ($divisors as $secondTerm) {
try {
bcdivmod($firstTerm, $secondTerm);
echo "NG\n";
} catch (Error $e) {
echo $e->getMessage() === 'Division by zero' ? 'OK' :'NG';
echo "\n";
}
}
}
?>
--EXPECT--
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
OK
Loading