diff --git a/NEWS b/NEWS index a4e00693b90f6..b820e43925caa 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,10 @@ PHP NEWS - Core: . Fix GH-14978 (The xmlreader extension phpize build). (Peter Kokot) +- BCMath: + . Adjust bcround()'s $mode parameter to only accept the RoundingMode + enum. (timwolla, saki) + - DOM: . Fix trampoline leak in xpath callables. (nielsdos) @@ -18,6 +22,7 @@ PHP NEWS - Standard: . Fix references in request_parse_body() options array. (nielsdos) + . Add RoundingMode enum. (timwolla, saki) - XSL: . Fix trampoline leak in xpath callables. (nielsdos) diff --git a/UPGRADING b/UPGRADING index 93654728efaf8..6e748906059da 100644 --- a/UPGRADING +++ b/UPGRADING @@ -344,6 +344,9 @@ PHP 8.4 UPGRADE NOTES . stream_bucket_make_writeable() and stream_bucket_new() will now return a StreamBucket instance instead of an stdClass instance. RFC: https://wiki.php.net/rfc/dedicated_stream_bucket + . Added a new RoundingMode enum with clearer naming and improved discoverability + compared to the PHP_ROUND_* constants. + RFC: https://wiki.php.net/rfc/correctly_name_the_rounding_mode_and_make_it_an_enum - SOAP: . Added support for clark notation for namespaces in class map. @@ -549,23 +552,27 @@ PHP 8.4 UPGRADE NOTES would have resulted in 1.0 instead of the correct result 0.0. Additional inputs might also be affected and result in different outputs compared to earlier PHP versions. - . The default value of the 'cost' option for PASSWORD_BCRYPT for password_hash() - has been increased from '10' to '12'. + . The $mode parameter of the round() function has been widened to RoundingMode|int, + accepting instances of a new RoundingMode enum. - RFC: https://wiki.php.net/rfc/bcrypt_cost_2023 - . Four new modes have been added to the round() function: PHP_ROUND_CEILING, - PHP_ROUND_FLOOR, PHP_ROUND_TOWARD_ZERO, PHP_ROUND_AWAY_FROM_ZERO. + RFC: https://wiki.php.net/rfc/correctly_name_the_rounding_mode_and_make_it_an_enum + . Four new modes have been added to the round() function: RoundingMode::PositiveInfinity, + RoundingMode::NegativeInfinity, RoundingMode::TowardsZero, RoundingMode::AwayFromZero. RFC: https://wiki.php.net/rfc/new_rounding_modes_to_round_function - . debug_zval_dump() now indicates whether an array is packed. . Fixed a bug caused by "pre-rounding" of the round() function. Previously, using "pre-rounding" to treat a value like 0.285 (actually 0.28499999999999998) as a decimal number and round it to 0.29. However, "pre-rounding" incorrectly rounds certain numbers, so this fix removes "pre-rounding" and changes the way numbers are compared, so that the values are correctly rounded as decimal numbers. - . long2ip() now returns string instead of string|false. . The maximum precision that can be handled by round() has been extended by one digit. + . The default value of the 'cost' option for PASSWORD_BCRYPT for password_hash() + has been increased from '10' to '12'. + + RFC: https://wiki.php.net/rfc/bcrypt_cost_2023 + . debug_zval_dump() now indicates whether an array is packed. + . long2ip() now returns string instead of string|false. . output_add_rewrite_var() now uses url_rewriter.hosts instead of session.trans_sid_hosts for selecting hosts that will be rewritten. . highlight_string() now has a return type of string|true instead of string|bool. @@ -809,12 +816,6 @@ PHP 8.4 UPGRADE NOTES . P_SID (NetBSD/FreeBSD only). . P_JAILID (FreeBSD only). -- Standard: - . PHP_ROUND_CEILING. - . PHP_ROUND_FLOOR. - . PHP_ROUND_TOWARD_ZERO. - . PHP_ROUND_AWAY_FROM_ZERO. - - Sockets: . SO_EXCLUSIVEADDRUSE (Windows only). . SOCK_CONN_DGRAM (NetBSD only). diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index cd73e6feb25ad..ec1cba2f7771f 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -696,15 +696,20 @@ PHP_FUNCTION(bcround) zend_string *numstr; zend_long precision = 0; zend_long mode = PHP_ROUND_HALF_UP; + zend_object *mode_object = NULL; bc_num num = NULL, result; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_STR(numstr) Z_PARAM_OPTIONAL Z_PARAM_LONG(precision) - Z_PARAM_LONG(mode) + Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce) ZEND_PARSE_PARAMETERS_END(); + if (mode_object != NULL) { + mode = php_math_round_mode_from_enum(mode_object); + } + switch (mode) { case PHP_ROUND_HALF_UP: case PHP_ROUND_HALF_DOWN: @@ -716,7 +721,8 @@ PHP_FUNCTION(bcround) case PHP_ROUND_AWAY_FROM_ZERO: break; default: - zend_argument_value_error(3, "must be a valid rounding mode (PHP_ROUND_*)"); + /* This is currently unreachable, but might become reachable when new modes are added. */ + zend_argument_value_error(3, "is an unsupported rounding mode"); return; } diff --git a/ext/bcmath/bcmath.stub.php b/ext/bcmath/bcmath.stub.php index 5f5e60d3804a8..eabc0a1042a1b 100644 --- a/ext/bcmath/bcmath.stub.php +++ b/ext/bcmath/bcmath.stub.php @@ -37,4 +37,4 @@ function bcfloor(string $num): string {} function bcceil(string $num): string {} /** @refcount 1 */ -function bcround(string $num, int $precision = 0, int $mode = PHP_ROUND_HALF_UP): string {} +function bcround(string $num, int $precision = 0, RoundingMode $mode = RoundingMode::HalfAwayFromZero): string {} diff --git a/ext/bcmath/bcmath_arginfo.h b/ext/bcmath/bcmath_arginfo.h index 91ab4fd156477..c3f01527b2b26 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: cd3d182e13cb0ca22b27c13a8d0a86c20fde5b76 */ + * Stub hash: 7439a5fca649b8c4df317b0da1416c8fe59faf72 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_bcadd, 0, 2, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, num1, IS_STRING, 0) @@ -52,7 +52,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_bcround, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, num, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, precision, IS_LONG, 0, "0") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "PHP_ROUND_HALF_UP") + ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, mode, RoundingMode, 0, "RoundingMode::HalfAwayFromZero") ZEND_END_ARG_INFO() ZEND_FUNCTION(bcadd); diff --git a/ext/bcmath/tests/bcround_all.phpt b/ext/bcmath/tests/bcround_all.phpt new file mode 100644 index 0000000000000..74d12fd427bc3 --- /dev/null +++ b/ext/bcmath/tests/bcround_all.phpt @@ -0,0 +1,104 @@ +--TEST-- +bcround() function all modes +--EXTENSIONS-- +bcmath +--FILE-- + %s\n", $mode->name, $number, bcround($number, 0, $mode)); + } + } +?> +--EXPECT-- +HalfAwayFromZero : 1.0 -> 1 +HalfAwayFromZero : -1.0 -> -1 +HalfAwayFromZero : 1.2 -> 1 +HalfAwayFromZero : -1.2 -> -1 +HalfAwayFromZero : 1.7 -> 2 +HalfAwayFromZero : -1.7 -> -2 +HalfAwayFromZero : 1.5 -> 2 +HalfAwayFromZero : -1.5 -> -2 +HalfAwayFromZero : 2.5 -> 3 +HalfAwayFromZero : -2.5 -> -3 +HalfTowardsZero : 1.0 -> 1 +HalfTowardsZero : -1.0 -> -1 +HalfTowardsZero : 1.2 -> 1 +HalfTowardsZero : -1.2 -> -1 +HalfTowardsZero : 1.7 -> 2 +HalfTowardsZero : -1.7 -> -2 +HalfTowardsZero : 1.5 -> 1 +HalfTowardsZero : -1.5 -> -1 +HalfTowardsZero : 2.5 -> 2 +HalfTowardsZero : -2.5 -> -2 +HalfEven : 1.0 -> 1 +HalfEven : -1.0 -> -1 +HalfEven : 1.2 -> 1 +HalfEven : -1.2 -> -1 +HalfEven : 1.7 -> 2 +HalfEven : -1.7 -> -2 +HalfEven : 1.5 -> 2 +HalfEven : -1.5 -> -2 +HalfEven : 2.5 -> 2 +HalfEven : -2.5 -> -2 +HalfOdd : 1.0 -> 1 +HalfOdd : -1.0 -> -1 +HalfOdd : 1.2 -> 1 +HalfOdd : -1.2 -> -1 +HalfOdd : 1.7 -> 2 +HalfOdd : -1.7 -> -2 +HalfOdd : 1.5 -> 1 +HalfOdd : -1.5 -> -1 +HalfOdd : 2.5 -> 3 +HalfOdd : -2.5 -> -3 +TowardsZero : 1.0 -> 1 +TowardsZero : -1.0 -> -1 +TowardsZero : 1.2 -> 1 +TowardsZero : -1.2 -> -1 +TowardsZero : 1.7 -> 1 +TowardsZero : -1.7 -> -1 +TowardsZero : 1.5 -> 1 +TowardsZero : -1.5 -> -1 +TowardsZero : 2.5 -> 2 +TowardsZero : -2.5 -> -2 +AwayFromZero : 1.0 -> 1 +AwayFromZero : -1.0 -> -1 +AwayFromZero : 1.2 -> 2 +AwayFromZero : -1.2 -> -2 +AwayFromZero : 1.7 -> 2 +AwayFromZero : -1.7 -> -2 +AwayFromZero : 1.5 -> 2 +AwayFromZero : -1.5 -> -2 +AwayFromZero : 2.5 -> 3 +AwayFromZero : -2.5 -> -3 +NegativeInfinity : 1.0 -> 1 +NegativeInfinity : -1.0 -> -1 +NegativeInfinity : 1.2 -> 1 +NegativeInfinity : -1.2 -> -2 +NegativeInfinity : 1.7 -> 1 +NegativeInfinity : -1.7 -> -2 +NegativeInfinity : 1.5 -> 1 +NegativeInfinity : -1.5 -> -2 +NegativeInfinity : 2.5 -> 2 +NegativeInfinity : -2.5 -> -3 +PositiveInfinity : 1.0 -> 1 +PositiveInfinity : -1.0 -> -1 +PositiveInfinity : 1.2 -> 2 +PositiveInfinity : -1.2 -> -1 +PositiveInfinity : 1.7 -> 2 +PositiveInfinity : -1.7 -> -1 +PositiveInfinity : 1.5 -> 2 +PositiveInfinity : -1.5 -> -1 +PositiveInfinity : 2.5 -> 3 +PositiveInfinity : -2.5 -> -2 diff --git a/ext/bcmath/tests/bcround_away_from_zero.phpt b/ext/bcmath/tests/bcround_away_from_zero.phpt index cfaa15002585e..df1a16ac18774 100644 --- a/ext/bcmath/tests/bcround_away_from_zero.phpt +++ b/ext/bcmath/tests/bcround_away_from_zero.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_AWAY_FROM_ZERO +bcround() function AwayFromZero --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/bcmath/tests/bcround_ceiling.phpt b/ext/bcmath/tests/bcround_ceiling.phpt index a3f6b50e1f989..5fd93c2621477 100644 --- a/ext/bcmath/tests/bcround_ceiling.phpt +++ b/ext/bcmath/tests/bcround_ceiling.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_CEILING +bcround() function PositiveInfinity --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/bcmath/tests/bcround_early_return.phpt b/ext/bcmath/tests/bcround_early_return.phpt index 372545a22ffa1..e1980dd3778c9 100644 --- a/ext/bcmath/tests/bcround_early_return.phpt +++ b/ext/bcmath/tests/bcround_early_return.phpt @@ -4,15 +4,6 @@ bcround() function with early return bcmath --FILE-- [], - 'PHP_ROUND_HALF_DOWN' => [], - 'PHP_ROUND_HALF_EVEN' => [], - 'PHP_ROUND_HALF_ODD' => [], - 'PHP_ROUND_FLOOR' => [], - 'PHP_ROUND_CEIL' => [], - 'PHP_ROUND_AWAY_FROM_ZERO' => [], - 'PHP_ROUND_TOWARD_ZERO' => [], + RoundingMode::HalfAwayFromZero->name => [], ]; foreach ($early_return_cases as [$num, $precision]) { - $result = str_pad("[{$num}, {$precision}]", 33, ' ', STR_PAD_LEFT) . ' => ' . bcround($num, $precision, PHP_ROUND_HALF_UP) . "\n"; + $result = str_pad("[{$num}, {$precision}]", 33, ' ', STR_PAD_LEFT) . ' => ' . bcround($num, $precision, RoundingMode::HalfAwayFromZero) . "\n"; echo $result; - $results['PHP_ROUND_HALF_UP'][] = $result; + $results[RoundingMode::HalfAwayFromZero->name][] = $result; } echo "\n"; -foreach ($otherModes as $mode) { +foreach (RoundingMode::cases() as $mode) { + $results[$mode->name] = []; foreach ($early_return_cases as [$num, $precision]) { - $result = str_pad("[{$num}, {$precision}]", 33, ' ', STR_PAD_LEFT) . ' => ' . bcround($num, $precision, constant($mode)) . "\n"; - $results[$mode][] = $result; + $result = str_pad("[{$num}, {$precision}]", 33, ' ', STR_PAD_LEFT) . ' => ' . bcround($num, $precision, $mode) . "\n"; + $results[$mode->name][] = $result; } - if ($results['PHP_ROUND_HALF_UP'] === $results[$mode]) { - echo str_pad($mode, 24, ' ', STR_PAD_LEFT) . ": result is same to PHP_ROUND_HALF_UP\n"; + if ($results[RoundingMode::HalfAwayFromZero->name] === $results[$mode->name]) { + echo str_pad($mode->name, 24, ' ', STR_PAD_LEFT) . ": result is same to HalfAwayFromZero\n"; } else { - echo str_pad($mode, 24, ' ', STR_PAD_LEFT) . ": result is not same to PHP_ROUND_HALF_UP, failed\n"; + echo str_pad($mode->name, 24, ' ', STR_PAD_LEFT) . ": result is not same to HalfAwayFromZero, failed\n"; } } ?> @@ -90,10 +75,11 @@ foreach ($otherModes as $mode) { [-0.0, 0] => 0 [-0.0000, 0] => 0 - PHP_ROUND_HALF_DOWN: result is same to PHP_ROUND_HALF_UP - PHP_ROUND_HALF_EVEN: result is same to PHP_ROUND_HALF_UP - PHP_ROUND_HALF_ODD: result is same to PHP_ROUND_HALF_UP - PHP_ROUND_FLOOR: result is same to PHP_ROUND_HALF_UP - PHP_ROUND_CEILING: result is same to PHP_ROUND_HALF_UP -PHP_ROUND_AWAY_FROM_ZERO: result is same to PHP_ROUND_HALF_UP - PHP_ROUND_TOWARD_ZERO: result is same to PHP_ROUND_HALF_UP + HalfAwayFromZero: result is same to HalfAwayFromZero + HalfTowardsZero: result is same to HalfAwayFromZero + HalfEven: result is same to HalfAwayFromZero + HalfOdd: result is same to HalfAwayFromZero + TowardsZero: result is same to HalfAwayFromZero + AwayFromZero: result is same to HalfAwayFromZero + NegativeInfinity: result is same to HalfAwayFromZero + PositiveInfinity: result is same to HalfAwayFromZero diff --git a/ext/bcmath/tests/bcround_error.phpt b/ext/bcmath/tests/bcround_error.phpt index d411763b49326..95579a4edf096 100644 --- a/ext/bcmath/tests/bcround_error.phpt +++ b/ext/bcmath/tests/bcround_error.phpt @@ -15,14 +15,7 @@ try { } catch (Throwable $e) { echo $e->getMessage()."\n"; } - -try { - bcround('0.001', 0, 1000); -} catch (Throwable $e) { - echo $e->getMessage()."\n"; -} ?> --EXPECT-- bcround(): Argument #1 ($num) is not well-formed bcround(): Argument #1 ($num) is not well-formed -bcround(): Argument #3 ($mode) must be a valid rounding mode (PHP_ROUND_*) diff --git a/ext/bcmath/tests/bcround_floor.phpt b/ext/bcmath/tests/bcround_floor.phpt index 88d7fcd9dbb29..346bc81e86e7c 100644 --- a/ext/bcmath/tests/bcround_floor.phpt +++ b/ext/bcmath/tests/bcround_floor.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_FLOOR +bcround() function NegativeInfinity --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/bcmath/tests/bcround_half_down.phpt b/ext/bcmath/tests/bcround_half_down.phpt index 03d3580248111..69a2a24ed8c24 100644 --- a/ext/bcmath/tests/bcround_half_down.phpt +++ b/ext/bcmath/tests/bcround_half_down.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_HALF_DOWN +bcround() function HalfTowardsZero --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/bcmath/tests/bcround_half_even.phpt b/ext/bcmath/tests/bcround_half_even.phpt index 955f0e14dcfd1..1413ca69ce123 100644 --- a/ext/bcmath/tests/bcround_half_even.phpt +++ b/ext/bcmath/tests/bcround_half_even.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_HALF_EVEN +bcround() function HalfEven --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/bcmath/tests/bcround_half_odd.phpt b/ext/bcmath/tests/bcround_half_odd.phpt index 941da75843f29..5207142152e9c 100644 --- a/ext/bcmath/tests/bcround_half_odd.phpt +++ b/ext/bcmath/tests/bcround_half_odd.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_HALF_ODD +bcround() function HalfOdd --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/bcmath/tests/bcround_half_up.phpt b/ext/bcmath/tests/bcround_half_up.phpt index 17c02c717e3a2..52129535f2d7f 100644 --- a/ext/bcmath/tests/bcround_half_up.phpt +++ b/ext/bcmath/tests/bcround_half_up.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_HALF_UP +bcround() function HalfAwayFromZero --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/bcmath/tests/bcround_test_helper.inc b/ext/bcmath/tests/bcround_test_helper.inc index c51efc46581b3..00adf18c52982 100644 --- a/ext/bcmath/tests/bcround_test_helper.inc +++ b/ext/bcmath/tests/bcround_test_helper.inc @@ -1,6 +1,6 @@ ", bcround($num, $precision, $mode), "\n"; @@ -8,7 +8,7 @@ function printResult (array $cases, int $mode) echo "\n"; } -function run_round_test(int $mode) +function run_round_test(RoundingMode $mode) { $non_boundary_value_cases = [ ['1.1', 0], diff --git a/ext/bcmath/tests/bcround_toward_zero.phpt b/ext/bcmath/tests/bcround_toward_zero.phpt index e9d0e277efe6e..2d7275ada9589 100644 --- a/ext/bcmath/tests/bcround_toward_zero.phpt +++ b/ext/bcmath/tests/bcround_toward_zero.phpt @@ -1,11 +1,11 @@ --TEST-- -bcround() function PHP_ROUND_TOWARD_ZERO +bcround() function TowardsZero --EXTENSIONS-- bcmath --FILE-- --EXPECT-- ========== non-boundary value ========== diff --git a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt index 5a230936fff96..47813255381e4 100644 --- a/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt @@ -5,18 +5,16 @@ Felix De Vliegher --FILE-- getClassNames()); +$classNames = $standard->getClassNames(); +sort($classNames); +foreach ($classNames as $className) { + echo $className, PHP_EOL; +} ?> --EXPECT-- -array(5) { - [0]=> - string(22) "__PHP_Incomplete_Class" - [1]=> - string(14) "AssertionError" - [2]=> - string(15) "php_user_filter" - [3]=> - string(12) "StreamBucket" - [4]=> - string(9) "Directory" -} +AssertionError +Directory +RoundingMode +StreamBucket +__PHP_Incomplete_Class +php_user_filter diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 1eddb342bcc28..7b3494303ca41 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -34,6 +34,7 @@ #include "ext/session/php_session.h" #include "zend_exceptions.h" #include "zend_attributes.h" +#include "zend_enum.h" #include "zend_ini.h" #include "zend_operators.h" #include "ext/standard/php_dns.h" @@ -304,6 +305,8 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ assertion_error_ce = register_class_AssertionError(zend_ce_error); + rounding_mode_ce = register_class_RoundingMode(); + BASIC_MINIT_SUBMODULE(var) BASIC_MINIT_SUBMODULE(file) BASIC_MINIT_SUBMODULE(pack) diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index d09fbd5ad32cf..68cc86e7b1fa5 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -375,26 +375,6 @@ * @cvalue PHP_ROUND_HALF_ODD */ const PHP_ROUND_HALF_ODD = UNKNOWN; -/** - * @var int - * @cvalue PHP_ROUND_CEILING - */ -const PHP_ROUND_CEILING = UNKNOWN; -/** - * @var int - * @cvalue PHP_ROUND_FLOOR - */ -const PHP_ROUND_FLOOR = UNKNOWN; -/** - * @var int - * @cvalue PHP_ROUND_TOWARD_ZERO - */ -const PHP_ROUND_TOWARD_ZERO = UNKNOWN; -/** - * @var int - * @cvalue PHP_ROUND_AWAY_FROM_ZERO - */ -const PHP_ROUND_AWAY_FROM_ZERO = UNKNOWN; /* crypt.c */ @@ -3145,8 +3125,19 @@ function ceil(int|float $num): float {} /** @compile-time-eval */ function floor(int|float $num): float {} +enum RoundingMode { + case HalfAwayFromZero; + case HalfTowardsZero; + case HalfEven; + case HalfOdd; + case TowardsZero; + case AwayFromZero; + case NegativeInfinity; + case PositiveInfinity; +} + /** @compile-time-eval */ -function round(int|float $num, int $precision = 0, int $mode = PHP_ROUND_HALF_UP): float {} +function round(int|float $num, int $precision = 0, int|RoundingMode $mode = RoundingMode::HalfAwayFromZero): float {} /** @compile-time-eval */ function sin(float $num): float {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 7421ae7e76623..00bc7c5509d06 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e3a7bf1e9f0df3e38f6280f91af70e54ce492a60 */ + * Stub hash: 504f4172ac1d64535719234888400063eb37361b */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -1627,7 +1627,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_round, 0, 1, IS_DOUBLE, 0) ZEND_ARG_TYPE_MASK(0, num, MAY_BE_LONG|MAY_BE_DOUBLE, NULL) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, precision, IS_LONG, 0, "0") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "PHP_ROUND_HALF_UP") + ZEND_ARG_OBJ_TYPE_MASK(0, mode, RoundingMode, MAY_BE_LONG, "RoundingMode::HalfAwayFromZero") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_sin, 0, 1, IS_DOUBLE, 0) @@ -3615,6 +3615,10 @@ static const zend_function_entry class_AssertionError_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_RoundingMode_methods[] = { + ZEND_FE_END +}; + static void register_basic_functions_symbols(int module_number) { REGISTER_LONG_CONSTANT("EXTR_OVERWRITE", PHP_EXTR_OVERWRITE, CONST_PERSISTENT); @@ -3704,10 +3708,6 @@ static void register_basic_functions_symbols(int module_number) REGISTER_LONG_CONSTANT("PHP_ROUND_HALF_DOWN", PHP_ROUND_HALF_DOWN, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_ROUND_HALF_EVEN", PHP_ROUND_HALF_EVEN, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_ROUND_HALF_ODD", PHP_ROUND_HALF_ODD, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PHP_ROUND_CEILING", PHP_ROUND_CEILING, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PHP_ROUND_FLOOR", PHP_ROUND_FLOOR, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PHP_ROUND_TOWARD_ZERO", PHP_ROUND_TOWARD_ZERO, CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PHP_ROUND_AWAY_FROM_ZERO", PHP_ROUND_AWAY_FROM_ZERO, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CRYPT_SALT_LENGTH", PHP_MAX_SALT_LEN, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CRYPT_STD_DES", 1, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CRYPT_EXT_DES", 1, CONST_PERSISTENT); @@ -4197,3 +4197,26 @@ static zend_class_entry *register_class_AssertionError(zend_class_entry *class_e return class_entry; } + +static zend_class_entry *register_class_RoundingMode(void) +{ + zend_class_entry *class_entry = zend_register_internal_enum("RoundingMode", IS_UNDEF, class_RoundingMode_methods); + + zend_enum_add_case_cstr(class_entry, "HalfAwayFromZero", NULL); + + zend_enum_add_case_cstr(class_entry, "HalfTowardsZero", NULL); + + zend_enum_add_case_cstr(class_entry, "HalfEven", NULL); + + zend_enum_add_case_cstr(class_entry, "HalfOdd", NULL); + + zend_enum_add_case_cstr(class_entry, "TowardsZero", NULL); + + zend_enum_add_case_cstr(class_entry, "AwayFromZero", NULL); + + zend_enum_add_case_cstr(class_entry, "NegativeInfinity", NULL); + + zend_enum_add_case_cstr(class_entry, "PositiveInfinity", NULL); + + return class_entry; +} diff --git a/ext/standard/math.c b/ext/standard/math.c index ecf614a6ffc46..2d0c8fc09e9ed 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -20,6 +20,7 @@ #include "php.h" #include "php_math.h" #include "zend_bitset.h" +#include "zend_enum.h" #include "zend_exceptions.h" #include "zend_multiply.h" #include "zend_portability.h" @@ -30,6 +31,8 @@ #include "basic_functions.h" +PHPAPI zend_class_entry *rounding_mode_ce; + /* {{{ php_intpow10 Returns pow(10.0, (double)power), uses fast lookup table for exact powers */ static inline double php_intpow10(int power) { @@ -301,6 +304,32 @@ PHP_FUNCTION(floor) } /* }}} */ +int php_math_round_mode_from_enum(zend_object *mode) +{ + zval *case_name = zend_enum_fetch_case_name(mode); + zend_string *mode_name = Z_STR_P(case_name); + + switch (ZSTR_VAL(mode_name)[0] + ZSTR_VAL(mode_name)[4]) { + case 'H' + 'A': + return PHP_ROUND_HALF_UP; + case 'H' + 'T': + return PHP_ROUND_HALF_DOWN; + case 'H' + 'E': + return PHP_ROUND_HALF_EVEN; + case 'H' + 'O': + return PHP_ROUND_HALF_ODD; + case 'T' + 'r': + return PHP_ROUND_TOWARD_ZERO; + case 'A' + 'F': + return PHP_ROUND_AWAY_FROM_ZERO; + case 'N' + 't': + return PHP_ROUND_FLOOR; + case 'P' + 't': + return PHP_ROUND_CEILING; + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + /* {{{ Returns the number rounded to specified precision */ PHP_FUNCTION(round) { @@ -308,12 +337,13 @@ PHP_FUNCTION(round) int places = 0; zend_long precision = 0; zend_long mode = PHP_ROUND_HALF_UP; + zend_object *mode_object = NULL; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_NUMBER(value) Z_PARAM_OPTIONAL Z_PARAM_LONG(precision) - Z_PARAM_LONG(mode) + Z_PARAM_OBJ_OF_CLASS_OR_LONG(mode_object, rounding_mode_ce, mode) ZEND_PARSE_PARAMETERS_END(); if (ZEND_NUM_ARGS() >= 2) { @@ -324,6 +354,10 @@ PHP_FUNCTION(round) } } + if (mode_object != NULL) { + mode = php_math_round_mode_from_enum(mode_object); + } + switch (mode) { case PHP_ROUND_HALF_UP: case PHP_ROUND_HALF_DOWN: @@ -335,7 +369,7 @@ PHP_FUNCTION(round) case PHP_ROUND_FLOOR: break; default: - zend_argument_value_error(3, "must be a valid rounding mode (PHP_ROUND_*)"); + zend_argument_value_error(3, "must be a valid rounding mode (RoundingMode::*)"); RETURN_THROWS(); } diff --git a/ext/standard/php_math_round_mode.h b/ext/standard/php_math_round_mode.h index d4c11a8312fb2..11a8e8732049e 100644 --- a/ext/standard/php_math_round_mode.h +++ b/ext/standard/php_math_round_mode.h @@ -15,6 +15,8 @@ +----------------------------------------------------------------------+ */ +#include "php.h" + /* Define rounding modes (all are round-to-nearest) */ #ifndef PHP_ROUND_HALF_UP #define PHP_ROUND_HALF_UP 0x01 /* Arithmetic rounding, up == away from zero */ @@ -47,3 +49,7 @@ #ifndef PHP_ROUND_AWAY_FROM_ZERO #define PHP_ROUND_AWAY_FROM_ZERO 0x08 #endif + +extern PHPAPI zend_class_entry *rounding_mode_ce; + +int php_math_round_mode_from_enum(zend_object *mode); diff --git a/ext/standard/tests/math/round_RoundingMode.phpt b/ext/standard/tests/math/round_RoundingMode.phpt new file mode 100644 index 0000000000000..c07186e0093ea --- /dev/null +++ b/ext/standard/tests/math/round_RoundingMode.phpt @@ -0,0 +1,102 @@ +--TEST-- +round(): Test RoundingMode enum. +--FILE-- + %+.17g\n", $mode->name, $number, round($number, 0, $mode)); + } +} +?> +--EXPECT-- +HalfAwayFromZero : +1 -> +1 +HalfAwayFromZero : -1 -> -1 +HalfAwayFromZero : +1.2 -> +1 +HalfAwayFromZero : -1.2 -> -1 +HalfAwayFromZero : +1.7 -> +2 +HalfAwayFromZero : -1.7 -> -2 +HalfAwayFromZero : +1.5 -> +2 +HalfAwayFromZero : -1.5 -> -2 +HalfAwayFromZero : +2.5 -> +3 +HalfAwayFromZero : -2.5 -> -3 +HalfTowardsZero : +1 -> +1 +HalfTowardsZero : -1 -> -1 +HalfTowardsZero : +1.2 -> +1 +HalfTowardsZero : -1.2 -> -1 +HalfTowardsZero : +1.7 -> +2 +HalfTowardsZero : -1.7 -> -2 +HalfTowardsZero : +1.5 -> +1 +HalfTowardsZero : -1.5 -> -1 +HalfTowardsZero : +2.5 -> +2 +HalfTowardsZero : -2.5 -> -2 +HalfEven : +1 -> +1 +HalfEven : -1 -> -1 +HalfEven : +1.2 -> +1 +HalfEven : -1.2 -> -1 +HalfEven : +1.7 -> +2 +HalfEven : -1.7 -> -2 +HalfEven : +1.5 -> +2 +HalfEven : -1.5 -> -2 +HalfEven : +2.5 -> +2 +HalfEven : -2.5 -> -2 +HalfOdd : +1 -> +1 +HalfOdd : -1 -> -1 +HalfOdd : +1.2 -> +1 +HalfOdd : -1.2 -> -1 +HalfOdd : +1.7 -> +2 +HalfOdd : -1.7 -> -2 +HalfOdd : +1.5 -> +1 +HalfOdd : -1.5 -> -1 +HalfOdd : +2.5 -> +3 +HalfOdd : -2.5 -> -3 +TowardsZero : +1 -> +1 +TowardsZero : -1 -> -1 +TowardsZero : +1.2 -> +1 +TowardsZero : -1.2 -> -1 +TowardsZero : +1.7 -> +1 +TowardsZero : -1.7 -> -1 +TowardsZero : +1.5 -> +1 +TowardsZero : -1.5 -> -1 +TowardsZero : +2.5 -> +2 +TowardsZero : -2.5 -> -2 +AwayFromZero : +1 -> +1 +AwayFromZero : -1 -> -1 +AwayFromZero : +1.2 -> +2 +AwayFromZero : -1.2 -> -2 +AwayFromZero : +1.7 -> +2 +AwayFromZero : -1.7 -> -2 +AwayFromZero : +1.5 -> +2 +AwayFromZero : -1.5 -> -2 +AwayFromZero : +2.5 -> +3 +AwayFromZero : -2.5 -> -3 +NegativeInfinity : +1 -> +1 +NegativeInfinity : -1 -> -1 +NegativeInfinity : +1.2 -> +1 +NegativeInfinity : -1.2 -> -2 +NegativeInfinity : +1.7 -> +1 +NegativeInfinity : -1.7 -> -2 +NegativeInfinity : +1.5 -> +1 +NegativeInfinity : -1.5 -> -2 +NegativeInfinity : +2.5 -> +2 +NegativeInfinity : -2.5 -> -3 +PositiveInfinity : +1 -> +1 +PositiveInfinity : -1 -> -1 +PositiveInfinity : +1.2 -> +2 +PositiveInfinity : -1.2 -> -1 +PositiveInfinity : +1.7 -> +2 +PositiveInfinity : -1.7 -> -1 +PositiveInfinity : +1.5 -> +2 +PositiveInfinity : -1.5 -> -1 +PositiveInfinity : +2.5 -> +3 +PositiveInfinity : -2.5 -> -2 diff --git a/ext/standard/tests/math/round_gh12143_expand-rounding-target.phpt b/ext/standard/tests/math/round_gh12143_expand-rounding-target.phpt index 71b3dc34f8fec..b96a7ffe3e3be 100644 --- a/ext/standard/tests/math/round_gh12143_expand-rounding-target.phpt +++ b/ext/standard/tests/math/round_gh12143_expand-rounding-target.phpt @@ -38,7 +38,7 @@ $testCases = [ [4503599627370495.5, 0], [-4503599627370495.5, 0], ], - 'PHP_ROUND_AWAY_FROM_ZERO' => [ + 'RoundingMode::AwayFromZero' => [ [0.12345678901234560, 16], [-0.12345678901234560, 16], [12345678901234567, -1], @@ -46,7 +46,7 @@ $testCases = [ [4503599627370495.5, 0], [-4503599627370495.5, 0], ], - 'PHP_ROUND_TOWARD_ZERO' => [ + 'RoundingMode::TowardsZero' => [ [0.12345678901234566, 16], [-0.12345678901234566, 16], [12345678901234565, -1], @@ -54,7 +54,7 @@ $testCases = [ [4503599627370495.5, 0], [-4503599627370495.5, 0], ], - 'PHP_ROUND_CEILING' => [ + 'RoundingMode::PositiveInfinity' => [ [0.12345678901234560, 16], [-0.12345678901234560, 16], [12345678901234564, -1], @@ -62,7 +62,7 @@ $testCases = [ [4503599627370495.5, 0], [-4503599627370495.5, 0], ], - 'PHP_ROUND_FLOOR' => [ + 'RoundingMode::NegativeInfinity' => [ [0.12345678901234560, 16], [-0.12345678901234560, 16], [12345678901234564, -1], @@ -113,7 +113,7 @@ float(-12345678901234570) float(4503599627370495) float(-4503599627370495) -========== PHP_ROUND_AWAY_FROM_ZERO ========== +========== RoundingMode::AwayFromZero ========== float(0.1234567890123456) float(-0.1234567890123456) float(12345678901234570) @@ -121,7 +121,7 @@ float(-12345678901234570) float(4503599627370496) float(-4503599627370496) -========== PHP_ROUND_TOWARD_ZERO ========== +========== RoundingMode::TowardsZero ========== float(0.1234567890123456) float(-0.1234567890123456) float(12345678901234560) @@ -129,7 +129,7 @@ float(-12345678901234560) float(4503599627370495) float(-4503599627370495) -========== PHP_ROUND_CEILING ========== +========== RoundingMode::PositiveInfinity ========== float(0.1234567890123456) float(-0.1234567890123456) float(12345678901234570) @@ -137,7 +137,7 @@ float(-12345678901234560) float(4503599627370496) float(-4503599627370495) -========== PHP_ROUND_FLOOR ========== +========== RoundingMode::NegativeInfinity ========== float(0.1234567890123456) float(-0.1234567890123456) float(12345678901234560) diff --git a/ext/standard/tests/math/round_modes.phpt b/ext/standard/tests/math/round_modes.phpt index 06a793211b3a7..3d27ca6e82bbc 100644 --- a/ext/standard/tests/math/round_modes.phpt +++ b/ext/standard/tests/math/round_modes.phpt @@ -7,10 +7,10 @@ $modes = [ "PHP_ROUND_HALF_DOWN" => PHP_ROUND_HALF_DOWN, "PHP_ROUND_HALF_EVEN" => PHP_ROUND_HALF_EVEN, "PHP_ROUND_HALF_ODD" => PHP_ROUND_HALF_ODD, - "PHP_ROUND_CEILING" => PHP_ROUND_CEILING, - "PHP_ROUND_FLOOR" => PHP_ROUND_FLOOR, - "PHP_ROUND_TOWARD_ZERO" => PHP_ROUND_TOWARD_ZERO, - "PHP_ROUND_AWAY_FROM_ZERO" => PHP_ROUND_AWAY_FROM_ZERO, + "PositiveInfinity" => RoundingMode::PositiveInfinity, + "NegativeInfinity" => RoundingMode::NegativeInfinity, + "TowardsZero" => RoundingMode::TowardsZero, + "AwayFromZero" => RoundingMode::AwayFromZero, ]; $numbers = [ @@ -455,7 +455,7 @@ mode: PHP_ROUND_HALF_ODD 0.0001 => 0.0001 -0.0001 => -0.0001 -mode: PHP_ROUND_CEILING +mode: PositiveInfinity precision: -1 2.5 => 10 -2.5 => -0 @@ -556,7 +556,7 @@ mode: PHP_ROUND_CEILING 0.0001 => 0.0001 -0.0001 => -0.0001 -mode: PHP_ROUND_FLOOR +mode: NegativeInfinity precision: -1 2.5 => 0 -2.5 => -10 @@ -657,7 +657,7 @@ mode: PHP_ROUND_FLOOR 0.0001 => 0.0001 -0.0001 => -0.0001 -mode: PHP_ROUND_TOWARD_ZERO +mode: TowardsZero precision: -1 2.5 => 0 -2.5 => -0 @@ -758,7 +758,7 @@ mode: PHP_ROUND_TOWARD_ZERO 0.0001 => 0.0001 -0.0001 => -0.0001 -mode: PHP_ROUND_AWAY_FROM_ZERO +mode: AwayFromZero precision: -1 2.5 => 10 -2.5 => -10 diff --git a/ext/standard/tests/math/round_modes_ceiling_and_floor.phpt b/ext/standard/tests/math/round_modes_ceiling_and_floor.phpt index 1b3a34f0f98ec..469395390904a 100644 --- a/ext/standard/tests/math/round_modes_ceiling_and_floor.phpt +++ b/ext/standard/tests/math/round_modes_ceiling_and_floor.phpt @@ -1,10 +1,7 @@ --TEST-- -round() with modes PHP_ROUND_CEILING and PHP_ROUND_FLOOR +round() with modes PositiveInfinity and NegativeInfinity --FILE-- --EXPECT-- -mode PHP_ROUND_CEILING +mode PositiveInfinity bool(true) bool(true) bool(true) @@ -52,7 +49,7 @@ bool(true) bool(true) bool(true) -mode PHP_ROUND_FLOOR +mode NegativeInfinity bool(true) bool(true) bool(true) diff --git a/ext/standard/tests/math/round_modes_zeros.phpt b/ext/standard/tests/math/round_modes_zeros.phpt index cdf97395dd9bf..ee972e54a110f 100644 --- a/ext/standard/tests/math/round_modes_zeros.phpt +++ b/ext/standard/tests/math/round_modes_zeros.phpt @@ -7,10 +7,10 @@ $modes = [ "PHP_ROUND_HALF_DOWN" => PHP_ROUND_HALF_DOWN, "PHP_ROUND_HALF_EVEN" => PHP_ROUND_HALF_EVEN, "PHP_ROUND_HALF_ODD" => PHP_ROUND_HALF_ODD, - "PHP_ROUND_CEILING" => PHP_ROUND_CEILING, - "PHP_ROUND_FLOOR" => PHP_ROUND_FLOOR, - "PHP_ROUND_TOWARD_ZERO" => PHP_ROUND_TOWARD_ZERO, - "PHP_ROUND_AWAY_FROM_ZERO" => PHP_ROUND_AWAY_FROM_ZERO, + "PositiveInfinity" => RoundingMode::PositiveInfinity, + "NegativeInfinity" => RoundingMode::NegativeInfinity, + "TowardsZero" => RoundingMode::TowardsZero, + "AwayFromZero" => RoundingMode::AwayFromZero, ]; $precisions = [-1, 0, 1, 2]; @@ -129,7 +129,7 @@ mode: PHP_ROUND_HALF_ODD 0.0 => 0 -0.0 => -0 -mode: PHP_ROUND_CEILING +mode: PositiveInfinity precision: -1 0 => 0 -0 => 0 @@ -154,7 +154,7 @@ mode: PHP_ROUND_CEILING 0.0 => 0 -0.0 => -0 -mode: PHP_ROUND_FLOOR +mode: NegativeInfinity precision: -1 0 => 0 -0 => 0 @@ -179,7 +179,7 @@ mode: PHP_ROUND_FLOOR 0.0 => 0 -0.0 => -0 -mode: PHP_ROUND_TOWARD_ZERO +mode: TowardsZero precision: -1 0 => 0 -0 => 0 @@ -204,7 +204,7 @@ mode: PHP_ROUND_TOWARD_ZERO 0.0 => 0 -0.0 => -0 -mode: PHP_ROUND_AWAY_FROM_ZERO +mode: AwayFromZero precision: -1 0 => 0 -0 => 0 diff --git a/ext/standard/tests/math/round_valid_rounding_mode.phpt b/ext/standard/tests/math/round_valid_rounding_mode.phpt index d61bc6356e1e6..d7e4655a29853 100644 --- a/ext/standard/tests/math/round_valid_rounding_mode.phpt +++ b/ext/standard/tests/math/round_valid_rounding_mode.phpt @@ -9,4 +9,4 @@ try { } ?> --EXPECT-- -round(): Argument #3 ($mode) must be a valid rounding mode (PHP_ROUND_*) +round(): Argument #3 ($mode) must be a valid rounding mode (RoundingMode::*)