[RFC] ext/bcmath: Added bcdivmod (#15740)

RFC: https://wiki.php.net/rfc/add_bcdivmod_to_bcmath

Added bcdivmod() function and added divmod() method to BcMath\Number class.
This commit is contained in:
Saki Takamachi 2024-09-23 06:43:11 +09:00 committed by GitHub
parent 2b90acb469
commit f6db576c31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 681 additions and 1 deletions

1
NEWS
View file

@ -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'>:

View file

@ -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().

View file

@ -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),

View file

@ -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)
{
@ -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;

View file

@ -19,6 +19,12 @@ namespace
/** @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 {}
@ -64,6 +70,9 @@ namespace BcMath
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 {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: f3101bbd25da90d97801d53ea673789d1ce4f6a1 */
* Stub hash: 687d6fb392a9b0c1329152cc0f62341a73e427f4 */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_bcadd, 0, 2, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, num1, IS_STRING, 0)
@ -15,6 +15,12 @@ ZEND_END_ARG_INFO()
#define arginfo_bcmod arginfo_bcadd
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_bcdivmod, 0, 2, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, num1, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, num2, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, scale, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_bcpowmod, 0, 3, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, num, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, exponent, IS_STRING, 0)
@ -72,6 +78,11 @@ ZEND_END_ARG_INFO()
#define arginfo_class_BcMath_Number_mod arginfo_class_BcMath_Number_add
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_BcMath_Number_divmod, 0, 1, IS_ARRAY, 0)
ZEND_ARG_OBJ_TYPE_MASK(0, num, BcMath\\\116umber, MAY_BE_STRING|MAY_BE_LONG, NULL)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, scale, IS_LONG, 1, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_BcMath_Number_powmod, 0, 2, BcMath\\\116umber, 0)
ZEND_ARG_OBJ_TYPE_MASK(0, exponent, BcMath\\\116umber, MAY_BE_STRING|MAY_BE_LONG, NULL)
ZEND_ARG_OBJ_TYPE_MASK(0, modulus, BcMath\\\116umber, MAY_BE_STRING|MAY_BE_LONG, NULL)
@ -117,6 +128,7 @@ ZEND_FUNCTION(bcsub);
ZEND_FUNCTION(bcmul);
ZEND_FUNCTION(bcdiv);
ZEND_FUNCTION(bcmod);
ZEND_FUNCTION(bcdivmod);
ZEND_FUNCTION(bcpowmod);
ZEND_FUNCTION(bcpow);
ZEND_FUNCTION(bcsqrt);
@ -131,6 +143,7 @@ ZEND_METHOD(BcMath_Number, sub);
ZEND_METHOD(BcMath_Number, mul);
ZEND_METHOD(BcMath_Number, div);
ZEND_METHOD(BcMath_Number, mod);
ZEND_METHOD(BcMath_Number, divmod);
ZEND_METHOD(BcMath_Number, powmod);
ZEND_METHOD(BcMath_Number, pow);
ZEND_METHOD(BcMath_Number, sqrt);
@ -148,6 +161,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(bcmul, arginfo_bcmul)
ZEND_FE(bcdiv, arginfo_bcdiv)
ZEND_FE(bcmod, arginfo_bcmod)
ZEND_FE(bcdivmod, arginfo_bcdivmod)
ZEND_FE(bcpowmod, arginfo_bcpowmod)
ZEND_FE(bcpow, arginfo_bcpow)
ZEND_FE(bcsqrt, arginfo_bcsqrt)
@ -166,6 +180,7 @@ static const zend_function_entry class_BcMath_Number_methods[] = {
ZEND_ME(BcMath_Number, mul, arginfo_class_BcMath_Number_mul, ZEND_ACC_PUBLIC)
ZEND_ME(BcMath_Number, div, arginfo_class_BcMath_Number_div, ZEND_ACC_PUBLIC)
ZEND_ME(BcMath_Number, mod, arginfo_class_BcMath_Number_mod, ZEND_ACC_PUBLIC)
ZEND_ME(BcMath_Number, divmod, arginfo_class_BcMath_Number_divmod, ZEND_ACC_PUBLIC)
ZEND_ME(BcMath_Number, powmod, arginfo_class_BcMath_Number_powmod, ZEND_ACC_PUBLIC)
ZEND_ME(BcMath_Number, pow, arginfo_class_BcMath_Number_pow, ZEND_ACC_PUBLIC)
ZEND_ME(BcMath_Number, sqrt, arginfo_class_BcMath_Number_sqrt, ZEND_ACC_PUBLIC)

View file

@ -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!

View file

@ -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

View file

@ -0,0 +1,157 @@
--TEST--
BcMath\Number div(), mod(), divmod() by zero
--EXTENSIONS--
bcmath
--FILE--
<?php
$values1 = ['100', '-100'];
$values2 = [
[0, 'int'],
[-0, 'int'],
['0', 'string'],
['-0', 'string'],
['0.000', 'string'],
['-0.000', 'string'],
[new BcMath\Number('0'), 'object'],
[new BcMath\Number('-0'), 'object'],
[new BcMath\Number('0.000'), 'object'],
[new BcMath\Number('-0.000'), 'object'],
];
foreach ($values1 as $value1) {
$num = new BcMath\Number($value1);
foreach ($values2 as [$value2, $type]) {
echo "{$value1} and {$value2}: {$type}\n";
echo "div: ";
try {
$num->div($value2);
echo "NG\n";
} catch (Error $e) {
echo $e->getMessage() === 'Division by zero' ? 'OK' :'NG';
echo "\n";
}
echo "mod: ";
try {
$num->mod($value2);
echo "NG\n";
} catch (Error $e) {
echo $e->getMessage() === 'Modulo by zero' ? 'OK' :'NG';
echo "\n";
}
echo "divmod: ";
try {
$num->divmod($value2);
echo "NG\n";
} catch (Error $e) {
echo $e->getMessage() === 'Division by zero' ? 'OK' :'NG';
echo "\n";
}
echo "\n";
}
}
?>
--EXPECT--
100 and 0: int
div: OK
mod: OK
divmod: OK
100 and 0: int
div: OK
mod: OK
divmod: OK
100 and 0: string
div: OK
mod: OK
divmod: OK
100 and -0: string
div: OK
mod: OK
divmod: OK
100 and 0.000: string
div: OK
mod: OK
divmod: OK
100 and -0.000: string
div: OK
mod: OK
divmod: OK
100 and 0: object
div: OK
mod: OK
divmod: OK
100 and 0: object
div: OK
mod: OK
divmod: OK
100 and 0.000: object
div: OK
mod: OK
divmod: OK
100 and 0.000: object
div: OK
mod: OK
divmod: OK
-100 and 0: int
div: OK
mod: OK
divmod: OK
-100 and 0: int
div: OK
mod: OK
divmod: OK
-100 and 0: string
div: OK
mod: OK
divmod: OK
-100 and -0: string
div: OK
mod: OK
divmod: OK
-100 and 0.000: string
div: OK
mod: OK
divmod: OK
-100 and -0.000: string
div: OK
mod: OK
divmod: OK
-100 and 0: object
div: OK
mod: OK
divmod: OK
-100 and 0: object
div: OK
mod: OK
divmod: OK
-100 and 0.000: object
div: OK
mod: OK
divmod: OK
-100 and 0.000: object
div: OK
mod: OK
divmod: OK

View file

@ -0,0 +1,226 @@
--TEST--
BcMath\Number divmod()
--EXTENSIONS--
bcmath
--FILE--
<?php
$values1 = ['100.012', '-100.012'];
$values2 = [
[100, 'int'],
[-30, 'int'],
['-20', 'string'],
['0.01', 'string'],
['-0.40', 'string'],
[new BcMath\Number('80.3'), 'object'],
[new BcMath\Number('-50.6'), 'object'],
];
foreach ($values1 as $value1) {
$num = new BcMath\Number($value1);
foreach ($values2 as [$value2, $type]) {
echo "{$value1} divmod {$value2}: {$type}\n";
[$quot, $rem] = $num->divmod($value2);
var_dump($quot, $rem);
echo "\n";
}
}
?>
--EXPECT--
100.012 divmod 100: int
object(BcMath\Number)#4 (2) {
["value"]=>
string(1) "1"
["scale"]=>
int(0)
}
object(BcMath\Number)#5 (2) {
["value"]=>
string(5) "0.012"
["scale"]=>
int(3)
}
100.012 divmod -30: int
object(BcMath\Number)#6 (2) {
["value"]=>
string(2) "-3"
["scale"]=>
int(0)
}
object(BcMath\Number)#7 (2) {
["value"]=>
string(6) "10.012"
["scale"]=>
int(3)
}
100.012 divmod -20: string
object(BcMath\Number)#5 (2) {
["value"]=>
string(2) "-5"
["scale"]=>
int(0)
}
object(BcMath\Number)#4 (2) {
["value"]=>
string(5) "0.012"
["scale"]=>
int(3)
}
100.012 divmod 0.01: string
object(BcMath\Number)#7 (2) {
["value"]=>
string(5) "10001"
["scale"]=>
int(0)
}
object(BcMath\Number)#6 (2) {
["value"]=>
string(5) "0.002"
["scale"]=>
int(3)
}
100.012 divmod -0.40: string
object(BcMath\Number)#4 (2) {
["value"]=>
string(4) "-250"
["scale"]=>
int(0)
}
object(BcMath\Number)#5 (2) {
["value"]=>
string(5) "0.012"
["scale"]=>
int(3)
}
100.012 divmod 80.3: object
object(BcMath\Number)#6 (2) {
["value"]=>
string(1) "1"
["scale"]=>
int(0)
}
object(BcMath\Number)#7 (2) {
["value"]=>
string(6) "19.712"
["scale"]=>
int(3)
}
100.012 divmod -50.6: object
object(BcMath\Number)#5 (2) {
["value"]=>
string(2) "-1"
["scale"]=>
int(0)
}
object(BcMath\Number)#4 (2) {
["value"]=>
string(6) "49.412"
["scale"]=>
int(3)
}
-100.012 divmod 100: int
object(BcMath\Number)#3 (2) {
["value"]=>
string(2) "-1"
["scale"]=>
int(0)
}
object(BcMath\Number)#6 (2) {
["value"]=>
string(6) "-0.012"
["scale"]=>
int(3)
}
-100.012 divmod -30: int
object(BcMath\Number)#4 (2) {
["value"]=>
string(1) "3"
["scale"]=>
int(0)
}
object(BcMath\Number)#5 (2) {
["value"]=>
string(7) "-10.012"
["scale"]=>
int(3)
}
-100.012 divmod -20: string
object(BcMath\Number)#6 (2) {
["value"]=>
string(1) "5"
["scale"]=>
int(0)
}
object(BcMath\Number)#3 (2) {
["value"]=>
string(6) "-0.012"
["scale"]=>
int(3)
}
-100.012 divmod 0.01: string
object(BcMath\Number)#5 (2) {
["value"]=>
string(6) "-10001"
["scale"]=>
int(0)
}
object(BcMath\Number)#4 (2) {
["value"]=>
string(6) "-0.002"
["scale"]=>
int(3)
}
-100.012 divmod -0.40: string
object(BcMath\Number)#3 (2) {
["value"]=>
string(3) "250"
["scale"]=>
int(0)
}
object(BcMath\Number)#6 (2) {
["value"]=>
string(6) "-0.012"
["scale"]=>
int(3)
}
-100.012 divmod 80.3: object
object(BcMath\Number)#4 (2) {
["value"]=>
string(2) "-1"
["scale"]=>
int(0)
}
object(BcMath\Number)#5 (2) {
["value"]=>
string(7) "-19.712"
["scale"]=>
int(3)
}
-100.012 divmod -50.6: object
object(BcMath\Number)#6 (2) {
["value"]=>
string(1) "1"
["scale"]=>
int(0)
}
object(BcMath\Number)#3 (2) {
["value"]=>
string(7) "-49.412"
["scale"]=>
int(3)
}

View file

@ -0,0 +1,43 @@
--TEST--
BcMath\Number divmod() with scale
--EXTENSIONS--
bcmath
--FILE--
<?php
$scales = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
$values1 = ['100.012', '-100.012'];
$values2 = [
[100, 'int'],
[-30, 'int'],
['-20', 'string'],
['0.01', 'string'],
['-0.40', 'string'],
[new BcMath\Number('80.3'), 'object'],
[new BcMath\Number('-50.6'), 'object'],
];
foreach ($scales as $scale) {
foreach ($values1 as $value1) {
$num = new BcMath\Number($value1);
foreach ($values2 as [$value2, $type]) {
[$func_quot, $func_rem] = bcdivmod($value1, (string) $value2, $scale);
[$method_quot, $method_rem] = $num->divmod($value2, $scale);
if ($method_quot->compare($func_quot) !== 0) {
echo "Quot is incorrect.\n";
var_dump($value1, $value2, $scale, $func_quot, $method_quot);
}
if ($method_rem->compare($func_rem) !== 0) {
echo "Rem is incorrect.\n";
var_dump($value1, $value2, $scale, $func_rem, $method_rem);
}
}
}
}
echo 'done!';
?>
--EXPECT--
done!