PHP RFC: Correctly name the rounding mode and make it an Enum
- Version: 1.1.0
- Date: 2024-04-21
- Author: Saki Takamachi (saki@php.net), Tim Düsterhus (timwolla@php.net)
- Status: Implemented
- Target Version: PHP 8.4
- First Published at: https://wiki.php.net/rfc/correctly_name_the_rounding_mode_and_make_it_an_Enum
Introduction
There are eight rounding modes in the current master branch (8.4):
const PHP_ROUND_HALF_UP const PHP_ROUND_HALF_DOWN const PHP_ROUND_HALF_EVEN const PHP_ROUND_HALF_ODD const PHP_ROUND_CEILING // Added in PHP 8.4. const PHP_ROUND_FLOOR // Added in PHP 8.4. const PHP_ROUND_TOWARD_ZERO // Added in PHP 8.4. const PHP_ROUND_AWAY_FROM_ZERO // Added in PHP 8.4.
There are two problems with these:
- First, the value is just an int, so we need to verify that the value passed is a valid rounding mode.
- The second problem is that the behavior of the “up” and “down” modes does not match the mathematical definition. Especially for non-native speakers, the different rounding modes might also be more easily confused, compared to a naming that uses less abstract terms.
See an example regarding the second problem:
round(1.5, 0, PHP_ROUND_HALF_UP); // 2 round(-1.5, 0, PHP_ROUND_HALF_UP); // -2 round(1.5, 0, PHP_ROUND_HALF_DOWN); // 1 round(-1.5, 0, PHP_ROUND_HALF_DOWN); // -1
When passed a negative value, PHP_ROUND_HALF_UP
and PHP_ROUND_HALF_DOWN
behave contrary to their names. This issue is mentioned by Gina on the internals mailinglist.
https://externals.io/message/123126
Proposal
This RFC proposes creating a new enum for rounding modes where the different rounding modes (enum cases) have more explicit names that clearly spell out the rounding behavior, without needing to consult the documentation.
Would suggest an Enum like this:
enum RoundingMode { /** * Round to the nearest integer. If the decimal part is 5, round to * the integer with the larger absolute value. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 1 * -1.2 -> -1 * 1.6 -> 2 * -1.6 -> -2 * 1.5 -> 2 * -1.5 -> -2 * 2.5 -> 3 * -2.5 -> -3 */ case HalfAwayFromZero; // PHP_ROUND_HALF_UP /** * Round to the nearest integer. If the decimal part is 5, round to * the integer with the smaller absolute value. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 1 * -1.2 -> -1 * 1.6 -> 2 * -1.6 -> -2 * 1.5 -> 1 * -1.5 -> -1 * 2.5 -> 2 * -2.5 -> -2 */ case HalfTowardsZero; // PHP_ROUND_HALF_DOWN /** * Round to the nearest integer. If the decimal part is 5, round to * the even integer. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 1 * -1.2 -> -1 * 1.6 -> 2 * -1.6 -> -2 * 1.5 -> 2 * -1.5 -> -2 * 2.5 -> 2 * -2.5 -> -2 */ case HalfEven; // PHP_ROUND_HALF_EVEN /** * Round to the nearest integer. If the decimal part is 5, round to * the odd integer. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 1 * -1.2 -> -1 * 1.6 -> 2 * -1.6 -> -2 * 1.5 -> 1 * -1.5 -> -1 * 2.5 -> 3 * -2.5 -> -3 */ case HalfOdd; // PHP_ROUND_HALF_ODD /** * Round to the nearest integer with a smaller or equal absolute value. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 1 * -1.2 -> -1 * 1.6 -> 1 * -1.6 -> -1 * 1.5 -> 1 * -1.5 -> -1 * 2.5 -> 2 * -2.5 -> -2 */ case TowardsZero; // PHP_ROUND_TOWARD_ZERO /** * Round to the nearest integer with a greater or equal absolute value. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 2 * -1.2 -> -2 * 1.6 -> 2 * -1.6 -> -2 * 1.5 -> 2 * -1.5 -> -2 * 2.5 -> 3 * -2.5 -> -3 */ case AwayFromZero; // PHP_ROUND_AWAY_FROM_ZERO /** * Round to the largest integer that is smaller or equal. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 1 * -1.2 -> -2 * 1.6 -> 1 * -1.6 -> -2 * 1.5 -> 1 * -1.5 -> -2 * 2.5 -> 2 * -2.5 -> -3 */ case NegativeInfinity; // PHP_ROUND_FLOOR /** * Round to the smallest integer that is greater or equal. * * 1.0 -> 1 * -1.0 -> -1 * 1.2 -> 2 * -1.2 -> -1 * 1.6 -> 2 * -1.6 -> -1 * 1.5 -> 2 * -1.5 -> -1 * 2.5 -> 3 * -2.5 -> -2 */ case PositiveInfinity; // PHP_ROUND_CEILING }
And then changing the signature of round()
to:
round(int|float $num, int $precision = 0, int|\RoundingMode $mode = \RoundingMode::HalfAwayFromZero): float
To avoid BC Break as much as possible, you can still use int
type arguments. However, this should be deprecated in the future (not included in this RFC).
Also, bcround()
will be added from the next minor version (8.4) due to the passing of the following RFC.
https://wiki.php.net/rfc/adding_bcround_bcfloor_bcceil_to_bcmath
If this RFC is passed, the rounding mode specified for bcround()
will be changed to use \RoundingMode
instead of int
. As for bcround()
, it doesn't accept int like round()
. This is because it is a feature that has not been released yet and BC Break will not occur.
The same goes for the following RFC that support BCMath objects: All rounding modes are replaced appropriately by \RoundingMode
. https://wiki.php.net/rfc/support_object_type_in_bcmath
To summarize, existing features will be changed to a union type of int|\RoundingMode
, and unreleased features will only accept \RoundingMode
as the rounding mode argument.
Rounding Modes introduced in PHP 8.4
The Add 4 new rounding modes to round() function introduced the rounding modes:
PHP_ROUND_CEILING
PHP_ROUND_FLOOR
PHP_ROUND_AWAY_FROM_ZERO
PHP_ROUND_TOWARD_ZERO
that have not yet appeared in a released version of PHP. In the interest of avoiding confusion by introducing both the new constants and the RoundingMode
enum in the same version and to prevent developers from using a new feature that is expected to be deprecated in a future version, the constants will be removed as part of this RFC. The 4 new rounding modes will only be accessible via the RoundingMode
enum.
Intl rounding mode
Intl also has a rounding mode, but unlike the round()
, it maps directly to the ICU library. Due to their slightly different nature, they are not included in the scope of this RFC.
Especially since NumberFormatter::setAttribute()
currently only accepts int|float
. Widening such a generic function feels wrong.
Backward Incompatible Changes
The \RoundingMode
class name will no longer be available to userland code. A GitHub search for symbol:RoundingMode “class RoundingMode” language:php
reveals 58 results. They all appear to be namespaced.
Proposed PHP Version(s)
Next minor release (8.4).
RFC Impact
To SAPIs
None.
To Existing Extensions
Standard and BCMath. However, the only released feature is Standard.
To Opcache
None.
New Constants
Describe any new constants so they can be accurately and comprehensively explained in the PHP documentation.
php.ini Defaults
None.
Open Issues
None.
Unaffected PHP Functionality
Only Standard and BCMath are affected.
Future Scope
Although not included in this RFC, it would be reasonable to deprecate existing rounding mode constants in the future. Also, in the future round()
should no longer accept int as the rounding mode argument type.
However, before deprecating the existing constant, can change the constant's value from an int
to a \RoundingMode
value to make the transition smoother:
const PHP_ROUND_HALF_UP = \RoundingMode::HalfAwayFromZero; const PHP_ROUND_HALF_DOWN = \RoundingMode::HalfTowardsZero; const PHP_ROUND_HALF_EVEN = \RoundingMode::HalfEven; const PHP_ROUND_HALF_ODD = \RoundingMode::HalfOdd;
Proposed Voting Choices
As per the voting RFC a yes/no vote with a 2/3 majority is needed for this proposal to be accepted.
Patches and Tests
Implementation
References
https://externals.io/message/122735#123020 (In a thread about BCMath, a suggestion was made to make the rounding mode Enum)
https://externals.io/message/123126 (It was mentioned that the name of the current rounding mode is a misnomer)
Rejected Features
None.