Recently I have had to serialize a double into text, and then get it back. The value seems to not be equivalent:
double d1 = 0.84551240822557006;
string s = d1.ToString("R");
double d2 = double.Parse(s);
bool s1 = d1 == d2;
// -> s1 is False
But according to MSDN: Standard Numeric Format Strings, the "R" option is supposed to guarantee round-trip safety.
The round-trip ("R") format specifier is used to ensure that a numeric value that is converted to a string will be parsed back into the same numeric value
Why did this happen?
Wow - a 3 year old question and everyone seems to have missed a point - even Jon Skeet! (@Jon: Respect. I hope I'm not making a fool of myself.)
For the record I ran the code sample and in my environment (Win10 x64 AnyCPU Debug, target .NetFx 4.7) the test after round-trip returned true.
Here is an experiment. Digits are aligned to help make the point...
This code...
Produces this output (the asterisks *** were added afterwards)...
It is done artificially but in the 1st section the loop counts in increments of decimal 0.0000000000000001.
Notice how two "consecutive values" (***) have the same internal binary representation.
In the 2nd section - because we're not jumping through hoops to force decimal addition - the internal value keeps ticking up in the least significant bit. The two sequences of 10 values go out of sync after 5 iterations.
The point is that (internally binary) doubles cannot have exact decimal representations and vice-versa.
We can only try to get a decimal string representing our value "as close as possible".
Here the R-formatted string 0.99999999999999745 is ambiguously "closest to" either 0.9999999999999974 or 0.9999999999999975.
I do appreciate that the question appears to "show this feature the other way around" (one decimal representation mapping ambiguously to two different binaries) but have not managed to recreate that.
We are right at the limit of the precision of doubles after all and that is why R-formatted strings are needed.
I like to think of it this way "The round-trip format specifier produces a string representing the nearest double value to your double value that can be round-tripped." in other words "the R-formatted string must be round-trip-able, not necessarily the value."
To labour the point, one should not assume that "value -> string -> same value" is possible but
should be able to rely on "value -> string -> nearby value -> same string -> same nearby value -> ...
Remember
The internal representation of doubles is dependent on environment/platform
Even in a fully-Microsoft ecosystem there are still many possible variations
a. Build options (x86/x64/AnyCPU, Release/Debug)
b. Hardware (Intel CPUs have the 80 bit register for arithmetic - which might be used differently by debug and release build code)
c. Who knows where the IL code might find itself running (32 bit mode under 64 bit on operating system X/Y etc)?
This should "fix" the code of the original question...
Recently, I'm trying to resolve this issue. As pointed out through the code , the double.ToString("R") has following logic:
In this case, double.ToString("R") wrongly chose the result in precision of 15 so the bug happens. There's an official workaround in the MSDN doc:
So unless this issue being resolved, you have to use double.ToString("G17") for round-tripping.
Update: Now there's a specific issue to track this bug.
It seems to me that this is simply a bug. Your expectations are entirely reasonable. I've reproduced it using .NET 4.5.1 (x64), running the following console app which uses my
DoubleConverter
class.DoubleConverter.ToExactString
shows the exact value represented by adouble
:Results in .NET:
Results in Mono 3.3.0:
If you manually specify the string from Mono (which contains the "006" on the end), .NET will parse that back to the original value. To it looks like the problem is in the
ToString("R")
handling rather than the parsing.As noted in other comments, it looks like this is specific to running under the x64 CLR. If you compile and run the above code targeting x86, it's fine:
... you get the same results as with Mono. It would be interesting to know whether the bug shows up under RyuJIT - I don't have that installed at the moment myself. In particular, I can imagine this possibly being a JIT bug, or it's quite possible that there are whole different implementations of the internals of
double.ToString
based on architecture.I suggest you file a bug at http://connect.microsoft.com
I found the bug.
.NET does the following in
clr\src\vm\comnumber.cpp
:DoubleToNumber
is pretty simple -- it just calls_ecvt
, which is in the C runtime:It turns out that
_ecvt
returns the string845512408225570
.Notice the trailing zero? It turns out that makes all the difference!
When the zero is present, the result actually parses back to
0.84551240822557006
, which is your original number -- so it compares equal, and hence only 15 digits are returned.However, if I truncate the string at that zero to
84551240822557
, then I get back0.84551240822556994
, which is not your original number, and hence it would return 17 digits.Proof: run the following 64-bit code (most of which I extracted from the Microsoft Shared Source CLI 2.0) in your debugger and examine
v
at the end ofmain
: