C# Formatting Floating Point Rounding/Accuracy Issue: N2 vs 0.00
Asked Answered
H

2

3

In C# (.Net 7):

float a = 13082987520.00f;
Console.WriteLine(a);
Console.WriteLine($"{a:N2}");
Console.WriteLine($"{a:0.00}");

I get:

1.3082988E+10
13,082,987,520.00
13082990000.00

In particular, notice the numerical difference between the last two lines:

13082987520
vs
13082990000

It's causing a lot of confusion when I try to write such numbers as CSV files. In this expected behaviour? How can we explain it?

Notice this question is VERY DIFFERENT FROM Difference between ToString("N2") and ToString("0.00"). I know the difference in terms of formatting, but here I am pointing out a potential unexpected implementation detail (and potentially a bug?) of floating point number printing. As shown in above case, apparently "N2" is NOT JUST adding thousand separators while "0.00" won't.

The same problem doesn't exist for double:

double b = 13082987520.00;
Console.WriteLine(b);
Console.WriteLine($"{b:N2}");
Console.WriteLine($"{b:0.00}");
13082987520
13,082,987,520.00
13082987520.00

The question here is:

  1. Which is the "correct" one for (single) floating point? (Actually it's the one printed with "N2")
  2. How do I print the correct one (which is the one shown using "N2") without having to do a.ToString("N2").Replace(",", string.Empty)?
Horseradish answered 17/10, 2023 at 13:53 Comment(1)
I think it's a bug or at least an unexpected behavior.Horseradish
L
4

From ECMA-334:2022 C# language standard, ch. 8.3.7:

Floating-point types

...

The float type can represent values ... with a precision of 7 digits.

(more on float point approximation in this question, simply 32-bit IEEE float point numbers cannot provide more than 7 digits precision)

So both values are correct approximation, both values rounded to 7 digits provide equal rounded values:

13082987520

13082990000
      ^
      |
1234567   

Float point numbers are good for approximate values. For exact values (hence e.g. for calculations on money) you should use decimal instead. From same standard ch. 8.3.8:

The decimal type is a 128-bit data type suitable for financial and monetary calculations.

Launder answered 25/10, 2023 at 16:57 Comment(1)
Well, that makes sense! I thought this only affects computing and didn't expect it affect formatting😂Horseradish
L
2

It's instructive to compare the behavior of the code running under .NET Framework 4.8.1 and .NET 7:

                                       // .NET FX 4.8.1     | .NET 7
Console.WriteLine(a);                  // 1.308299E+10      | 1.3082988E+10
Console.WriteLine(a.ToString("N2"));   // 13,082,990,000.00 | 13,082,987,520.00
Console.WriteLine(a.ToString("0.00")); // 13082990000.00    | 13082990000.00

Which of these formatted values is correct?

Arguably, all of them are "correct". They agree with the value of a to seven decimal digits of precision, which is all that the float data type promises to support.

Why are the formatted values different between .NET Framework and .NET 7?

.NET Core 3.0 improved the floating-point formatting code to make it compliant with the IEEE 754-2008 standard. ToString() now returns the shortest round-trippable string, whereas ToString("N2") returns all digits available in the exact floating-point value.

In .NET 7, why do custom format strings like "0.00" round float values after seven digits?

Apparently, this behavior is for backward compatibility:

// SinglePrecisionCustomFormat and DoublePrecisionCustomFormat are used to ensure that
// custom format strings return the same string as in previous releases when the format
// would return x digits or less (where x is the value of the corresponding constant).
private const int SinglePrecisionCustomFormat = 7;

How can I obtain the same value as "N2" but without commas?

Try using the "F2" fixed-point format instead:

Console.WriteLine($"{a:F2}"); // 13082987520.00
Lemcke answered 28/10, 2023 at 22:14 Comment(1)
That's very helpful with plenty of insights! Thanks for pointing out F2 formatting option!Horseradish

© 2022 - 2025 — McMap. All rights reserved.