-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
strconv: FormatFloat rounding error #29491
Comments
I'm not sure I understand what issue you are reporting. The
And this seems to hold for both your numbers:
Note how |
As you can see in my example using precision 0 for "integers" result in a value which after rounding should give another result while still being compatible with both ParseFloat and V8/EcmaScript. This is a request rather than a bug. I did port 2000 lines to .NET but that wasn't a pleasure :( |
Although a bit ugly the following workaround |
I'm not sure if this falls into a proposed change since (as noted above) the documentation only states that precision -1 outputs one of the shortest decimal representations that satisfies the round trip property, but I think the expectation is that the -1 precision output is also correctly rounded to the closest decimal representation to the input. Note that issue #2625 was resolved to produce the closest decimal. I believe that closest decimal is what other programming languages typically provide as well. The bug appears to be in this section of roundShortest:
The round up comparison is byte-by-byte and so incorrectly fails for these inputs:
49848468198408557 < 49848468198408560 so should have
58339553793802237 < 58339553793802240 so should have Adding @rsc as the author of the conversion routine. |
The rounding issue also applies to a small set of floating point numbers:
|
That case is also the same issue with byte-wise comparison to '0'
73922251744542716 < 73922251744542720 so should have |
Change https://golang.org/cl/157697 mentions this issue: |
Though this bug has been around for a long time, I also happened to independently discover it a few days ago while implementing Ryu (see #15672 (comment)). I sent a CL though it will have to wait for Go 1.13. Thanks @bmkessler for the very helpful analysis. |
@cespare I tried the Java version of Ryu on x0000000000000001 and it returned 4.9e-324 which differs from the current Go implementation which returns 5e-324 (which also is compliant with JavaScript). |
@cyberphone that's just a bug in the Java code. See ulfjack/ryu#83. My Ryu code gives 5e-324. |
@cespare Do you think I should try to run my 100M test suite against your Ryu implementation? https://github.com/cyberphone/json-canonicalization/tree/master/testdata#es6-numbers |
The Java implementation attempts to follow the specification of Java's Double.toString [1], which says that it must return at least two digits. Out of all two-digit numbers, 4.9e-324 is closest to the exact floating point value in this case, so that's what it returns. The problem described in ulfjack/ryu#83 is that it sometimes doesn't return the closest two-digit number because there's a one-digit number that's shorter. The C implementation doesn't do that, and I have not found any differences between it and Grisu over a fairly large number of tests. [1] https://docs.oracle.com/javase/7/docs/api/java/lang/Double.html#toString(double)
|
I updated the README for Ryu, and have a feature request open for the Java implementation to generate shortest output as well at ulfjack/ryu#87. Thanks @cyberphone for bringing this to my attention. |
Float formatting uses a multiprecision fallback path where Grisu3 algorithm fails. This has a bug during the rounding phase: the difference between the decimal value and the upper bound is examined byte-by-byte and doesn't properly handle the case where the first divergence has a difference of 1. For instance (using an example from golang#29491), for the number 498484681984085570, roundShortest examines the three decimal values: lower: 498484681984085536 d: 498484681984085568 upper: 498484681984085600 After examining the 16th digit, we know that rounding d up will fall within the bounds unless all remaining digits of d are 9 and all remaining digits of upper are 0: d: ...855xx upper: ...856xx However, the loop forgets that d and upper have already diverged and then on the next iteration sees that the 17th digit of d is actually lower than the 17th digit of upper and decides that we still can't round up: d: ...8556x upper: ...8560x Thus the original value is incorrectly rounded down to 498484681984085560 instead of the closer (and equally short) 498484681984085570. Thanks to Brian Kessler for diagnosing this bug. Fix it by remembering when we've seen divergence in previous digits. This CL also fixes another bug in the same loop: for some inputs, the decimal value d or the lower bound may have fewer digits than the upper bound, yet the iteration through the digits starts at i=0 for each of them. For instance, given the float64 value 1e23, we have d: 99999999999999991611392 upper: 100000000000000000000000 but the loop starts by comparing '9' to '1' rather than '0' to '1'. I haven't found any cases where this second bug causes incorrect output because when the digit comparison fails on the first loop iteration the upper bound always has more nonzero digits (i.e., the expression 'i+1 < upper.nd' is always true). Fixes golang#29491 Change-Id: I58856a7a2e47935ec2f233d9f717ef15c78bb2d0
Float formatting uses a multiprecision fallback path where Grisu3 algorithm fails. This has a bug during the rounding phase: the difference between the decimal value and the upper bound is examined byte-by-byte and doesn't properly handle the case where the first divergence has a difference of 1. For instance (using an example from golang#29491), for the number 498484681984085570, roundShortest examines the three decimal values: lower: 498484681984085536 d: 498484681984085568 upper: 498484681984085600 After examining the 16th digit, we know that rounding d up will fall within the bounds unless all remaining digits of d are 9 and all remaining digits of upper are 0: d: ...855xx upper: ...856xx However, the loop forgets that d and upper have already diverged and then on the next iteration sees that the 17th digit of d is actually lower than the 17th digit of upper and decides that we still can't round up: d: ...8556x upper: ...8560x Thus the original value is incorrectly rounded down to 498484681984085560 instead of the closer (and equally short) 498484681984085570. Thanks to Brian Kessler for diagnosing this bug. Fix it by remembering when we've seen divergence in previous digits. This CL also fixes another bug in the same loop: for some inputs, the decimal value d or the lower bound may have fewer digits than the upper bound, yet the iteration through the digits starts at i=0 for each of them. For instance, given the float64 value 1e23, we have d: 99999999999999991611392 upper: 100000000000000000000000 but the loop starts by comparing '9' to '1' rather than '0' to '1'. I haven't found any cases where this second bug causes incorrect output because when the digit comparison fails on the first loop iteration the upper bound always has more nonzero digits (i.e., the expression 'i+1 < upper.nd' is always true). Fixes golang#29491 Change-Id: I58856a7a2e47935ec2f233d9f717ef15c78bb2d0
At exponent 64, every 1250th float64 is affected by the issue (and is fixed by the patch)
Output:
|
Not if it is the same bug, but this one is relatively small.
The above output "255.25", but I expect "255.26". go version go1.12.1 darwin/amd64 |
No, 255.25 is the correct rounding. float64(255.255) is slightly less than the actual 255.255.
|
Apparently JavaScript agree:
The rounding is performed at the binary level which is one reason to why floating point is not suitable for monetary amounts |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
I'm trying to implement a JSON canonicalization scheme requiring numbers to be expressed in the shortest way. prec -1 seemed like a better alternative than porting V8's solution but unfortunately it seems that the -1 option is broken:
What did you expect to see?
What did you see instead?
The text was updated successfully, but these errors were encountered: