rfc:correctly_name_the_rounding_mode_and_make_it_an_enum

PHP 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:

  1. First, the value is just an int, so we need to verify that the value passed is a valid rounding mode.
  2. 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.

Add the RoundingMode enum as described?
Real name Yes No
adiel (adiel)  
alcaeus (alcaeus)  
asgrim (asgrim)  
ashnazg (ashnazg)  
beberlei (beberlei)  
crell (crell)  
derick (derick)  
devnexen (devnexen)  
dragoonis (dragoonis)  
ericmann (ericmann)  
galvao (galvao)  
girgias (girgias)  
heiglandreas (heiglandreas)  
jimw (jimw)  
josh (josh)  
kalle (kalle)  
kocsismate (kocsismate)  
mauricio (mauricio)  
mbeccati (mbeccati)  
nielsdos (nielsdos)  
ocramius (ocramius)  
petk (petk)  
pierrick (pierrick)  
ramsey (ramsey)  
rasmus (rasmus)  
reywob (reywob)  
saki (saki)  
santiagolizardo (santiagolizardo)  
sergey (sergey)  
theodorejb (theodorejb)  
thorstenr (thorstenr)  
timwolla (timwolla)  
weierophinney (weierophinney)  
zeriyoshi (zeriyoshi)  
Final result: 34 0
This poll has been closed.

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.

rfc/correctly_name_the_rounding_mode_and_make_it_an_enum.txt · Last modified: 2024/07/18 18:45 by timwolla