Skip to content

RFC: Add the RoundingMode enum #14833

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 9 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 8 additions & 2 deletions ext/bcmath/bcmath.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 a rounding mode that is not supported");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this can happen now? As the value should always be a correct mode from the enum object, turning this into an assertion seems more logical.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot happen now (as the comment indicates), but can happen if additional cases are added to the RoundingMode enum in the future that cannot (easily) be supported with BCMath or where the implementation forgets to adjust BCMath.

This error message is thus intended to fail safely rather than crashing or returning bogus values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have this be an assertion and have a test in BC Math that cycles through all the cases of the enum to be sure we don't miss any new ones introduced.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an additional test based on round_RoundingMode.phpt that goes through all the enum cases, but I've preserved the error message instead of replacing it by an assert. The RoundingMode enum is owned by ext/standard, not ext/bcmath, so to me defensive programming makes sense there.

return;
}

Expand Down
2 changes: 1 addition & 1 deletion ext/bcmath/bcmath.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
4 changes: 2 additions & 2 deletions ext/bcmath/bcmath_arginfo.h

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

4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_away_from_zero.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_AWAY_FROM_ZERO
bcround() function AwayFromZero
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_AWAY_FROM_ZERO);
run_round_test(RoundingMode::AwayFromZero);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_ceiling.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_CEILING
bcround() function PositiveInfinity
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_CEILING);
run_round_test(RoundingMode::PositiveInfinity);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
50 changes: 18 additions & 32 deletions ext/bcmath/tests/bcround_early_return.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ bcround() function with early return
bcmath
--FILE--
<?php
$otherModes = [
'PHP_ROUND_HALF_DOWN',
'PHP_ROUND_HALF_EVEN',
'PHP_ROUND_HALF_ODD',
'PHP_ROUND_FLOOR',
'PHP_ROUND_CEILING',
'PHP_ROUND_AWAY_FROM_ZERO',
'PHP_ROUND_TOWARD_ZERO',
];

$early_return_cases = [
['123', -4],
Expand All @@ -38,33 +29,27 @@ $early_return_cases = [
];

$results = [
'PHP_ROUND_HALF_UP' => [],
'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";
}
}
?>
Expand All @@ -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
2 changes: 1 addition & 1 deletion ext/bcmath/tests/bcround_error.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ try {
--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_*)
bcround(): Argument #3 ($mode) must be of type RoundingMode, int given
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_floor.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_FLOOR
bcround() function NegativeInfinity
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_FLOOR);
run_round_test(RoundingMode::NegativeInfinity);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_half_down.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_HALF_DOWN
bcround() function HalfTowardsZero
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_HALF_DOWN);
run_round_test(RoundingMode::HalfTowardsZero);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_half_even.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_HALF_EVEN
bcround() function HalfEven
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_HALF_EVEN);
run_round_test(RoundingMode::HalfEven);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_half_odd.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_HALF_ODD
bcround() function HalfOdd
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_HALF_ODD);
run_round_test(RoundingMode::HalfOdd);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_half_up.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_HALF_UP
bcround() function HalfAwayFromZero
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_HALF_UP);
run_round_test(RoundingMode::HalfAwayFromZero);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_test_helper.inc
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<?php

function printResult (array $cases, int $mode)
function printResult (array $cases, RoundingMode $mode)
{
foreach ($cases as [$num, $precision]) {
echo str_pad("[{$num}, {$precision}]", 17, ' ', STR_PAD_LEFT), " => ", bcround($num, $precision, $mode), "\n";
}
echo "\n";
}

function run_round_test(int $mode)
function run_round_test(RoundingMode $mode)
{
$non_boundary_value_cases = [
['1.1', 0],
Expand Down
4 changes: 2 additions & 2 deletions ext/bcmath/tests/bcround_toward_zero.phpt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
--TEST--
bcround() function PHP_ROUND_TOWARD_ZERO
bcround() function TowardsZero
--EXTENSIONS--
bcmath
--FILE--
<?php
require_once __DIR__ . '/bcround_test_helper.inc';
run_round_test(PHP_ROUND_TOWARD_ZERO);
run_round_test(RoundingMode::TowardsZero);
?>
--EXPECT--
========== non-boundary value ==========
Expand Down
24 changes: 11 additions & 13 deletions ext/reflection/tests/ReflectionExtension_getClassNames_basic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,16 @@ Felix De Vliegher <felix.devliegher@gmail.com>
--FILE--
<?php
$standard = new ReflectionExtension('standard');
var_dump($standard->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
3 changes: 3 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
33 changes: 12 additions & 21 deletions ext/standard/basic_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down Expand Up @@ -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 {}
Expand Down
Loading
Loading