.net core 3 yields different floating point results from version 2.2

.NET Core introduced a lot of floating point parsing and formatting improvements in IEEE floating point compliance. One of them is IEEE 754-2008 formatting compliance.

Before .NET Core 3.0, ToString() internally limited precision to “just” 15 places, producing string that couldn’t be parsed back to the original. The question’s values differ by a single bit.

In both .NET 4.7 and .NET Core 3, the actual bytes remains the same. In both cases, calling

BitConverter.GetBytes(d*d*d)

Produces

85, 14, 45, 178, 157, 111, 27, 64

On the other hand, BitConverter.GetBytes(6.859) produces :

86, 14, 45, 178, 157, 111, 27, 64

Even in .NET Core 3, parsing “6.859” produces the second byte sequence :

BitConverter.GetBytes(double.Parse("6.859"))

This is a single bit difference. The old behavior produced a string that couldn’t be parsed back to the original value

The difference is explained by this change :

ToString(), ToString(“G”), and ToString(“R”) will now return the shortest roundtrippable string. This ensures that users end up with something that just works by default.

That’s why we always need to specify a precision when dealing with floating point numbers. There were improvements in this case too :

For the “G” format specifier that takes a precision (e.g. G3), the precision specifier is now always respected. For double with precisions less than 15 (inclusive) and for float with precisions less than 6 (inclusive) this means you get the same string as before. For precisions greater than that, you will get up to that many significant digits

Using ToString("G15") produces 6.859 while ToString("G16") produces 6.858999999999999, which has 16 fractional digits.

That’s a reminder that we always need to specify a precision when working with floating point numbers, whether it’s comparing or formatting

Leave a Comment