Reputation: 2397
Attention: This is not a question about lack of precision in floats when doing mathematical operations or comparisons. It is about formatting issues, though the confusion caused may leak in the description.
Background:
A log entry of our application essentially said that 19
was greater than 19
.
Reproduction: The mathematical steps leading to the issue could be found out, and the problem can be reproduced in a simple test:
[TestMethod]
public void WtfTest()
{
float bottom = -51;
float top = 19;
float rowHeight = (float)((top - bottom) / 3.0);
float line1 = bottom + rowHeight;
float line2 = line1 + rowHeight;
float line3 = line2 + rowHeight;
Assert.IsFalse(line3 > top, $"WTF: line3={line3:n50} is greater than top={top:n50}.");
}
This test fails because line3
is actually greater than top
, as can be checked e.g. with BitConverter.GetBytes
. That's NOT the issue here.
The real issue is the failure message which perfectly reproduces the original problem:
Assert.IsFalse failed. WTF: line3=19,00000000000000000000000000000000000000000000000000 is greater than top=19,00000000000000000000000000000000000000000000000000.
You see: the two numbers shown are perfectly equal. Though they are actually different.
Additional findings:
Calculating the difference and formatting it yields 0,00000190734900000000000000000000000000000000000000
which seems correct.
Debugging the test and looking at the values in VisualStudio's "Locals" window shows 19.0000019
for line3
.
Environment: .Net Framework 4.8, platform target x64, on Windows 10
Final question: How can I get the number formatting right?
Upvotes: 2
Views: 97
Reputation: 27046
According to the documentation, the recommended format for float is "G9" and for double is "G17".
When used with a Double value, the "G17" format specifier ensures that the original Double value successfully round-trips. This is because Double is an IEEE 754-2008-compliant double-precision (binary64) floating-point number that gives up to 17 significant digits of precision.
When used with a Single value, the "G9" format specifier ensures that the original Single value successfully round-trips. This is because Single is an IEEE 754-2008-compliant single-precision (binary32) floating-point number that gives up to nine significant digits of precision.
I've tested the following code on .net framework and .net core
float bottom = -51;
float top = 19;
float rowHeight = (float)((top - bottom) / 3.0);
float line1 = bottom + rowHeight;
float line2 = line1 + rowHeight;
float line3 = line2 + rowHeight;
Console.WriteLine(line3.ToString("G9"));
Console.WriteLine(line3.ToString("N50"));
Here is the result
//.net framework
19.0000019
19.00000000000000000000000000000000000000000000000000
//.net core
19.0000019
19.00000190734863281250000000000000000000000000000000
Upvotes: 3