From f6db576c3162dfb3cd1fa5437c0eb70836d4c2ab Mon Sep 17 00:00:00 2001 From: Saki Takamachi <34942839+SakiTakamachi@users.noreply.github.com> Date: Mon, 23 Sep 2024 06:43:11 +0900 Subject: [PATCH] [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. --- NEWS | 1 + UPGRADING | 2 + Zend/Optimizer/zend_func_infos.h | 1 + ext/bcmath/bcmath.c | 114 +++++++++ ext/bcmath/bcmath.stub.php | 9 + ext/bcmath/bcmath_arginfo.h | 17 +- ext/bcmath/tests/bcdivmod.phpt | 46 ++++ ext/bcmath/tests/bcdivmod_by_zero.phpt | 66 +++++ .../tests/number/methods/div_mod_by_zero.phpt | 157 ++++++++++++ ext/bcmath/tests/number/methods/divmod.phpt | 226 ++++++++++++++++++ .../number/methods/divmod_with_scale.phpt | 43 ++++ 11 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 ext/bcmath/tests/bcdivmod.phpt create mode 100644 ext/bcmath/tests/bcdivmod_by_zero.phpt create mode 100644 ext/bcmath/tests/number/methods/div_mod_by_zero.phpt create mode 100644 ext/bcmath/tests/number/methods/divmod.phpt create mode 100644 ext/bcmath/tests/number/methods/divmod_with_scale.phpt diff --git a/NEWS b/NEWS index 852504d4e7e..ee9ee810b25 100644 --- a/NEWS +++ b/NEWS @@ -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 : diff --git a/UPGRADING b/UPGRADING index 2ebdaafef64..97740040a0e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -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(). diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 5db20f79de0..8751ff30c69 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -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), diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index 924e63f50f8..b67a871b501 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -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, ", &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("); + 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, ", &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, "_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; diff --git a/ext/bcmath/bcmath.stub.php b/ext/bcmath/bcmath.stub.php index 79c1569e846..854a1d35497 100644 --- a/ext/bcmath/bcmath.stub.php +++ b/ext/bcmath/bcmath.stub.php @@ -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 {} diff --git a/ext/bcmath/bcmath_arginfo.h b/ext/bcmath/bcmath_arginfo.h index 12a0fa6800e..886be0292a1 100644 --- a/ext/bcmath/bcmath_arginfo.h +++ b/ext/bcmath/bcmath_arginfo.h @@ -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) diff --git a/ext/bcmath/tests/bcdivmod.phpt b/ext/bcmath/tests/bcdivmod.phpt new file mode 100644 index 00000000000..d69248c1ca4 --- /dev/null +++ b/ext/bcmath/tests/bcdivmod.phpt @@ -0,0 +1,46 @@ +--TEST-- +bcdivmod() function +--EXTENSIONS-- +bcmath +--INI-- +bcmath.scale=0 +--FILE-- + +--EXPECT-- +done! diff --git a/ext/bcmath/tests/bcdivmod_by_zero.phpt b/ext/bcmath/tests/bcdivmod_by_zero.phpt new file mode 100644 index 00000000000..4cf8111fc49 --- /dev/null +++ b/ext/bcmath/tests/bcdivmod_by_zero.phpt @@ -0,0 +1,66 @@ +--TEST-- +bcdivmod() function div by zero +--EXTENSIONS-- +bcmath +--INI-- +bcmath.scale=0 +--FILE-- +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 diff --git a/ext/bcmath/tests/number/methods/div_mod_by_zero.phpt b/ext/bcmath/tests/number/methods/div_mod_by_zero.phpt new file mode 100644 index 00000000000..3dc76e20712 --- /dev/null +++ b/ext/bcmath/tests/number/methods/div_mod_by_zero.phpt @@ -0,0 +1,157 @@ +--TEST-- +BcMath\Number div(), mod(), divmod() by zero +--EXTENSIONS-- +bcmath +--FILE-- +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 diff --git a/ext/bcmath/tests/number/methods/divmod.phpt b/ext/bcmath/tests/number/methods/divmod.phpt new file mode 100644 index 00000000000..a8070188aff --- /dev/null +++ b/ext/bcmath/tests/number/methods/divmod.phpt @@ -0,0 +1,226 @@ +--TEST-- +BcMath\Number divmod() +--EXTENSIONS-- +bcmath +--FILE-- +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) +} diff --git a/ext/bcmath/tests/number/methods/divmod_with_scale.phpt b/ext/bcmath/tests/number/methods/divmod_with_scale.phpt new file mode 100644 index 00000000000..7ff4227b180 --- /dev/null +++ b/ext/bcmath/tests/number/methods/divmod_with_scale.phpt @@ -0,0 +1,43 @@ +--TEST-- +BcMath\Number divmod() with scale +--EXTENSIONS-- +bcmath +--FILE-- +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!