PHP RFC: Correct the behavior of round() to follow proper floating-point semantics.
- Version: 0.9
- Date: 2025-06-25
- Author: Saki Takamachi, saki@php.net
- Status: Draft
- Implementation: yet
Introduction
In 2023, PHP RFC: Change the edge case of round() was rejected. That proposal aimed to invalidate the definition set by https://wiki.php.net/rfc/rounding , and to correct the behavior of round()
to follow proper floating-point semantics.
<?php /* Since 0.285 is actually approximated as 0.28499999999999998, * rounding it to 0.28 is the correct behavior from a floating-point perspective. */ round(0.285, 2); // 0.29 ?>
However, among the votes against the previous proposal, some expressed support for the overall direction but opposed targeting a minor version like 8.4 due to the scale of the change.
Additionally, at the time of the previous vote, there was no alternative available for users who preferred the old behavior. With the introduction of bcround()
in BCMath in PHP 8.4, we are now in a position to offer a suitable alternative.
Proposal
This RFC, like the previous one, proposes modifying the round()
function to follow correct floating-point behavior.
“Correct floating-point behavior” means that a floating-point value is always treated as a single, consistent approximation. Even if the result appears strange when viewed as a decimal, the behavior should reflect the inherent imprecision of floating-point arithmetic as it is, without attempting to correct it for visual consistency.
In most cases, the result remains unchanged, but in certain cases, the outcome may differ.
<?php round(0.285, 2); // 0.29 => 0.28 round(1.555, 2); // 1.56 => 1.55 round(0.285, 3, RoundingMode::TowardsZero); // 0.285 => 0.284 ?>
The reason for such outcomes is that, for example, the correct floating-point approximation of 0.285
is actually 0.28499999999999998
. As a result, it may not meet the edge case threshold for RoundingMode::HalfAwayFromZero
, or may end up being off by 1
when rounded RoundingMode::TowardsZero
.
Issues with the current implementation
In the current implementation, values that should not be considered edge cases in floating-point arithmetic are sometimes treated as such, leading to the following issues.
<?php var_dump(8.000000000000000 === 8.0000000000000005); // true round(8.0, 15, RoundingMode::HalfAwayFromZero); // 8.000000000000002 round(8.0, 15, RoundingMode::AwayFromZero); // 8 round(8.0, 15, RoundingMode::PositiveInfinity); // 8 ?>
This occurs in cases where, at extremely high precision, values ending in 0
and those ending in 5
become indistinguishable due to floating-point error.
Such inconsistencies arise when decimal-like behavior is forcibly applied to floating-point numbers. Floating-point values inherently represent a range of possible values due to precision limitations, and to handle them correctly, a single consistent approximation should be used within that range at all times.
These contradictions primarily stem from how edge cases are currently handled under RoundingMode::HalfAwayFromZero
, RoundingMode::AwayFromZero
, and RoundingMode::PositiveInfinity
. The implementation prioritizes decimal-like visual behavior, selectively interpreting “convenient” parts of the floating-point range for each case. As a result, the interpretation of the same floating-point value can vary depending on the rounding mode, leading to inconsistent behavior.
Backward Incompatible Changes
The behavior of round()
function near edge cases will be changed.
Additionally, number_format()
, which internally relies on this functionality, will also be affected.
Proposed PHP Version(s)
PHP 9.0
RFC Impact
To the Ecosystem
This change will affect userland code by altering the behavior of round()
and number_format()
in certain edge cases.
However, it only applies to a very specific subset of those edge cases.
To Existing Extensions
ext/standard
To SAPIs
None.
Open Issues
None.
Future Scope
None.
Voting Choices
Please consult the php/policies repository for the current voting guidelines.
Patches and Tests
yet.
Implementation
yet.
References
Rejected Features
None..
Changelog
If there are major changes to the initial proposal, please include a short summary with a date or a link to the mailing list announcement here, as not everyone has access to the wikis' version history.