Skip to content

Commit

Permalink
[RFC] ext/bcmath: Added bcdivmod (php#15740)
Browse files Browse the repository at this point in the history
RFC: https://wiki.php.net/rfc/add_bcdivmod_to_bcmath

Added bcdivmod() function and added divmod() method to BcMath\Number class.
  • Loading branch information
SakiTakamachi authored Sep 22, 2024
1 parent 2b90acb commit f6db576
Show file tree
Hide file tree
Showing 11 changed files with 681 additions and 1 deletion.
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

0 comments on commit f6db576

Please sign in to comment.