rfc:change_the_edge_case_of_round

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:change_the_edge_case_of_round [2023/11/07 16:16] sakirfc:change_the_edge_case_of_round [2023/12/08 00:37] (current) saki
Line 1: Line 1:
 ====== PHP RFC: Change the edge case of round() ====== ====== PHP RFC: Change the edge case of round() ======
-  * Version: 0.2.0+  * Version: 0.4.1
   * Date: 2023-10-23   * Date: 2023-10-23
   * Author: Saki Takamachi, saki@sakiot.com   * Author: Saki Takamachi, saki@sakiot.com
-  * Status: Under Discussion+  * Status: Declined
   * First Published at: https://wiki.php.net/rfc/change_the_edge_case_of_round   * First Published at: https://wiki.php.net/rfc/change_the_edge_case_of_round
  
Line 12: Line 12:
 However, there is an opinion that it is wrong to expect decimal-like behavior because FP is just FP. Since 0.28499999999999995 to 0.28500000000000000 are all the same IEEE754 internal representation, and most of their values ​​are less than 0.285, it is unreasonable to treat them as edge cases. However, there is an opinion that it is wrong to expect decimal-like behavior because FP is just FP. Since 0.28499999999999995 to 0.28500000000000000 are all the same IEEE754 internal representation, and most of their values ​​are less than 0.285, it is unreasonable to treat them as edge cases.
  
-==== FP is not a decimal number ====+==== About the current implementation ====
  
-When we treat FP "like" a decimal number, it is not an exact value, but an approximation with some error range. The following six numbers are all 3fd23d70a3d70a3d in IEEE754 representation.+Here are excerpts from the RFC that determined the current implementation policy that are particularly relevant in this context. 
 + 
 +<blockquote> 
 +If the requested number of places to round the number is smaller than the precision of the number, then the number will be first rounded to its own precision and then rounded to the requested number of places. 
 + 
 +Example: Round 1.255 to 2 places precision, expected value is 1.26. First step: Calculate 10^places = 10^2 = 100. Second step: Calculate 14 - floor(log10(value)) = 14 - 0 = 14 which indicates the number of places after the decimal point which are guaranteed to be exact by IEEE 754. Now, 2 < 14, so the condition applies. So, calculate 10^14 and multiply the number by that: 1.255 * 1e14 = 125499999999999.984375... Now, round that number to integer, i.e. 125500000000000. Now, divide that number by 10^(14 - 2) = 10^12 (the difference) and get 125.5 (exact). NOW round that number to decimal which yields 126 and divide it by 10^2 = 100 which gives 1.26 which is the expected result for that rounding operation. 
 + 
 +Of course, one may argue that pre-rounding is not necessary and that this is simply the problem with FP arithmetics. This is true on the one hand, but the introduction of the places parameter made it clear that round() is to operate as if the numbers were stored as decimals. We can't revert that and this seems to me to be the best solutions for FP numbers one can get. 
 +</blockquote> 
 + 
 +In other words, due to the presence of the second parameter, the user expects round() to behave as if it were dealing with a decimal number. 
 + 
 +By the way, the double rounding method has side effects and can lead to incorrect results. It will need to be fixed regardless of the outcome of this RFC. I have already created a PR to correct the issue in accordance with PHP current policy. If this RFC is rejected, this PR will be adopted. 
 +https://github.com/php/php-src/pull/12268 
 + 
 +We can discuss this RFC without getting caught up in the technical issues of whether it is feasible. 
 + 
 +==== Arguments against current implementation ==== 
 + 
 +FP is not a decimal number. When we treat FP "like" a decimal number, it is not an exact value, but an approximation with some error range. The following six numbers are all 3fd23d70a3d70a3d in IEEE754 representation.
  
 <code> <code>
Line 39: Line 58:
 You'll soon see that this becomes false. However, treating FP as a decimal number means that the above code must be true. I think you can understand that  is strange. There is no reason why only round() should behave as a decimal. You'll soon see that this becomes false. However, treating FP as a decimal number means that the above code must be true. I think you can understand that  is strange. There is no reason why only round() should behave as a decimal.
  
-==== The current implementation is wrong ==== +==== Expecting FP to behave like "FP" ====
- +
-Based on the above two reasons, I believe that the current round() edge case judgment criteria are incorrect.+
  
 Even if we treat FP like a decimal number, we should use the value that is closest to it, and not a value that is "just within the range" that is on the boundary with the next value.  Even if we treat FP like a decimal number, we should use the value that is closest to it, and not a value that is "just within the range" that is on the boundary with the next value. 
Line 53: Line 70:
  
 It can be seen that PHP's policy is not to require FP to behave as a decimal number. And as it says in the warning, if we want to calculate as a decimal number, we should use BCMath. It can be seen that PHP's policy is not to require FP to behave as a decimal number. And as it says in the warning, if we want to calculate as a decimal number, we should use BCMath.
 +
 +==== Reference: For other languages ====
 +
 +Similar to PHP, we investigated languages ​​that allow you to specify the precision you want to round.
 +
 +Ruby:
 +<code>
 +p 0.285.round(2)
 +// 0.29
 +
 +p 0.28499999999999998.round(2)
 +// 0.29
 +</code>
 +
 +Python:
 +<code>
 +// Python rounds to even numbers, so 0.285 cannot be compared. Instead, I use 1.555, which is also 1.5549... due to the FP error.
 +
 +print(round(1.555, 2))
 +// 1.55
 +
 +print(round(1.5549999999999999, 2))
 +// 1.55
 +</code>
 +
 +Ruby behaves the same as current PHP, and Python behaves the same as this RFC aims to do.
 +
 +==== Reference: Databases ====
 +
 +When it comes to databases, there are two types of values: exact values ​​and approximate values. Verify using approximate values ​​to align with PHP. Since even rounding may occur, I use 1.555, just like in Python.
 +
 +MySQL:
 +<code>
 +mysql> SELECT 1555E-3 = 15549999999999999E-16;
 ++---------------------------------+
 +| 1555E-3 = 15549999999999999E-16 |
 ++---------------------------------+
 +|                               1 |
 ++---------------------------------+
 +
 +mysql> SELECT ROUND(1555E-3, 2);
 ++-------------------+
 +| ROUND(1555E-3, 2) |
 ++-------------------+
 +|              1.56 |
 ++-------------------+
 +
 +mysql> SELECT ROUND(15549999999999999E-16, 2);
 ++---------------------------------+
 +| ROUND(15549999999999999E-16, 2) |
 ++---------------------------------+
 +|                            1.56 |
 ++---------------------------------+
 +</code>
 +
 +SQLite3:
 +<code>
 +sqlite> SELECT 1555E-3 = 15549999999999999E-16;
 +1
 +sqlite> SELECT ROUND(1555E-3, 2);
 +1.56
 +sqlite> SELECT ROUND(15549999999999999E-16, 2);
 +1.56
 +</code>
 +
 +Firebird:
 +<code>
 +SQL> SELECT 1555E-3 FROM RDB$DATABASE;
 +
 +               CONSTANT 
 +======================= 
 +1.554999999999999937828 
 +
 +SQL> SELECT ROUND(1555E-3, 2) FROM RDB$DATABASE;
 +
 +                  ROUND 
 +======================= 
 +1.560000000000000053291 
 +</code>
 +
 +SqlServer:
 +<code>
 +1> SELECT 1555E-3;
 +2> go
 +                        
 +------------------------
 +      1.5549999999999999
 +
 +1> SELECT ROUND(1555E-3, 2);
 +2> go
 +                        
 +------------------------
 +                    1.55
 +</code>
 +
 +MySQL, SQLite, Firebird are similar to current PHP. SqlServer is similar to what this RFC is aiming for.
 +
 +PostgreSQL cannot round double precision values ​​if we specify digits, so omit it.
  
 ===== Proposal ===== ===== Proposal =====
Line 60: Line 175:
 // previous behavior // previous behavior
 var_dump(round(0.285, 2)); // float(0.29) var_dump(round(0.285, 2)); // float(0.29)
 +var_dump(round(0.28499999999999998, 2)); // float(0.29)
  
 // new behavior // new behavior
 var_dump(round(0.285, 2)); // float(0.28) var_dump(round(0.285, 2)); // float(0.28)
 +var_dump(round(0.28499999999999998, 2)); // float(0.28)
 </code> </code>
  
Line 97: Line 214:
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
-We will probably vote for or against adding these functions. This requires 2/3 majority.+ 
 +As per the voting RFC a yes/no vote with a 2/3 majority is needed for this proposal to be accepted. 
 +Voting started on 2023-11-24 and will end on 2023-12-08 00:00 GMT. 
 +<doodle title="Change the edge case of round()" auth="Saki Takamachi" voteType="single" closed="false" closeon="2023-12-08T00:00:00Z"> 
 +   * Yes 
 +   * No 
 +</doodle>
  
 ===== Implementation ===== ===== Implementation =====
rfc/change_the_edge_case_of_round.1699373803.txt.gz · Last modified: 2023/11/07 16:16 by saki