-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix GH-12143: Improved handling of adjusting result digits #12162
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
Fix GH-12143: Improved handling of adjusting result digits #12162
Conversation
<?php
ini_set('precision', -1);
ini_set('serialize_precision', -1);
$v = 0.005;
for ($i = 0; $i < 9; $i++) {
$round = round($v, 2);
$v = number_format($v, 3);
$arr[$i] = 'round($v, 2) -> ' . $round; // string
$v = (float) $v;
$float[$i] = $v;
$v = ($v) + (0.01);
}
var_dump($arr, $float); // var_dump use serialize_precision
echo $v; // special precision for zero conversion to string
?> Expected result:
This is a fix for the more precise float rfc which uses -1, it is not recommended to use number_format but it is recommended to write the code without loops instead. |
Thank you. However, based on the issue related this test, we should not test with changing the floating point precision. This is a very convenient method when using php, but I don't think it's suitable for the purpose of this test. I agree with using |
Can I ask you for a review? |
I will try to take a proper look to all of this on Thursday or Friday. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have been looking through all of this, reading some FP resources the old RFC / email that introduced this and can't really see why that helper should be used in pre-rounding. I might need to do a bit more research but from what I gather it makes sense to do it this way.
var_dump(round(1.700000000000145, 13, PHP_ROUND_HALF_UP)); | ||
var_dump(round(-1.700000000000145, 13, PHP_ROUND_HALF_UP)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you maybe add more tests to cover more large number test cases and different rounding modes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally at least the three numbers in the title of the original issue would explicitly be tested for all rounding modes:
Considering that this fixes more edge case and changing somewhat the algorithm I would prefer to target master only. |
5cd31c6
to
f80526f
Compare
@bukka |
It may be necessary after all since I failed the test. I take a closer look. I found that if you don't do pre-round, or rather adjust the number of digits in advance, the helper will not be able to check 0.5, so behavior such as HALF DOWN will become strange. |
a8fc337
to
bcf01cf
Compare
@TimWolla @bukka
It seems like the behavior is a little counterintuitive. places = 13
places = 12
others:
|
I think it makes sense to change the pre-round behavior. However, I seem to have discovered another problem.
|
Reading this document, it seems like modes like https://wiki.php.net/rfc/rounding In other words, if you still follow this philosophy, it's behavior should be as follows.
The changes may have a bigger impact than you expected. |
I think this is probably the basis for the "pre round" threshold of If |
It is also possible to implement an implementation that supports rounding with higher precision than DBL_DIG while leaving the digit adjustment based on DBL_DIG as is. However, in that case, the following specifications will remain unchanged.
|
This is not a technical issue, but rather a specification issue, so if you have any opinions, I would love to hear them. |
Sorry, I don't understand that. Rounding |
Yes, that should result in 2, because the input is clearly across the halfway-mark. |
My expectation as a user is that numbers are rounded to the nearest integer (with the halfway point being decided based on the rounding mode) based on actual bit pattern in memory. If I pass in a number that is not exactly representable it will implicitly be rounded. This is unavoidable. But when I then perform explicit rounding to a certain precision I expected it to behave accurately as far as possible. Specifically I expect rounding to behave correctly if I specify a precision of |
Rounding |
As mentioned above, the result will be:
Based on the conversations we have had so far and what we have investigated, we have found that the above results are incorrect, so we are retracting this statement.
For example, if you try to round I think that the implementation of the existing code probably takes such phenomena into account. But So I'm starting to think that my worries may be unfounded. |
In the first place, "pre-round" seems to be done to widen the scope of application of edge cases. Therefore, I think the correct answer is to eliminate the "pre-round" processing itself altogether. |
When I tried removing "pre-round "completely, a problem occurred.
removed pre-round
use pre-round + my fix
The last digit of the floating point number is really unreliable since something like this happens just by moving the decimal point position... |
This method didn't work. Too many edge cases.
|
Pre-round was probably necessary because of this problem. I am very confused whether to include |
There are at least two problems.
|
@SakiTakamachi yes it's the real number for 17 digit <?php
ini_set('precision', -1);
ini_set('serialize_precision', -1); // -1 from php 7.1 or set 3 for all php version
$float = 0.285;
$resultfloat = round($float, 2);
var_dump('Uses zend_dtoa()\'s mode 0 precision:', $float, $resultfloat);
ini_set('precision', 18);
ini_set('serialize_precision', 18);
var_dump('Uses 18 and 17 precision:', $float, $resultfloat);
/*math.c _php_math_round example without pre_round
var_dump($float * 1e15 / 1e13 / 1e2);*/
?>
``
Expected result:
```code
string(36) "Uses zend_dtoa()'s mode 0 precision:"
float(0.285)
float(0.29)
string(25) "Uses 18 and 17 precision:"
float(0.284999999999999976)
float(0.28999999999999998) If $float "display" with 17 digit is: 0.28499999999999998, with 18 digit is: 0.284999999999999976 and with 16 digit is: 0.285 |
I might have come up with a better way. However, I'm very sleepy so I'll continue tomorrow... |
I've created a PR that completely fixes the issue. |
This PR fixes #12143
Two types of problems were discovered in this issue, this will fix one of them.
During the "pre round" process that adjusts the number of digits of values greater than
1e14
to1e14
,round()
results may lead to incorrect because rounding is done.Therefore, I have removed the process of rounding and simply converted them to integers using
floor
orceil
.incorrect example:
If you want to figure out what's going on, this comment of @jorgsowa 's is easy to understand.
#12143 (comment)
This change causes the following tests to fail:
https://github.com/php/php-src/blob/PHP-8.1/ext/standard/tests/math/bug24142.phpt
In this test, it added
0.01
using afor
loop and checked the result ofround()
.With this fix, the test was failing due to the following:
In the first place, it is impossible to perform accurate calculations with floating point numbers. If we add
0.01
at multiple times, the error will increase.0.084999999999999992
is not0.085
.It should not be necessary to absorb such "error accumulation" with
round()
.Therefore, I stopped using
for
in the test and rewrote all 9 lines.(How does everyone do review requests? It seems I don't have that authority.)