What is the rounding rule when the last digit is 5 in .NET?

5.2k Views Asked by At

Here is my code:

using static System.Console;

namespace ConsoleApp2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            double[] doubles = new[] { 9.05, 9.15, 9.25, 9.35, 9.45, 9.55, 9.65, 9.75, 9.85, 9.95 };
            foreach (double n in doubles)
            {
                WriteLine("{0} ===> {1:F1}", n, n);
            }

        }
    }
}

Output in .NET Framework 4.7.2:

9.05 ===> 9.1
9.15 ===> 9.2
9.25 ===> 9.3
9.35 ===> 9.4
9.45 ===> 9.5
9.55 ===> 9.6
9.65 ===> 9.7
9.75 ===> 9.8
9.85 ===> 9.9
9.95 ===> 10.0

Output in .NET 6 (with same code):

9.05 ===> 9.1
9.15 ===> 9.2
9.25 ===> 9.2
9.35 ===> 9.3
9.45 ===> 9.4
9.55 ===> 9.6
9.65 ===> 9.7
9.75 ===> 9.8
9.85 ===> 9.8
9.95 ===> 9.9

So, in .NET Framework, the numbers are rounded just like we were taught in school. Which is called round half up in Wikipedia.

But in .NET 6, 9.05, 9.15, 9.55, 9.65, 9.75 are rounded up, while 9.25, 9.35, 9.45, 9.85, 9.95 are rounded down.

I know there is a rule called round half to even – rounds to the nearest value; if the number falls midway, it is rounded to the nearest value with an even least significant digit.

But this is obviously not round half to even, some numbers are rounded to odd.

How can we explain the difference in .NET Framework 4.7.2 with .NET 6 and how can I just round the numbers in the same way as .NET Framework in .NET 6?

2

There are 2 best solutions below

9
David Browne - Microsoft On

Use decimal, not double, otherwise you're not starting with the exact values you think you are, and you get the expected results.

9.05 ===> 9.1
9.15 ===> 9.2
9.25 ===> 9.3
9.35 ===> 9.4
9.45 ===> 9.5
9.55 ===> 9.6
9.65 ===> 9.7
9.75 ===> 9.8
9.85 ===> 9.9
9.95 ===> 10.0

With doubles, most of the values are slightly off from the decimal literals in the code, and so are rounded to the nearest number. Only two are actually at the midpoint, and are rounded to even in .NET Core. But as @Traveller points out this isn't general rounding behavior; it's specific to how floating point numbers are printed.

9.05000000000000071E+000 ===> 9.1 <- rounded to nearest
9.15000000000000036E+000 ===> 9.2 <- rounded to nearest
9.25000000000000000E+000 ===> 9.2 <- rounded to even
9.34999999999999964E+000 ===> 9.3 <- rounded to nearest
9.44999999999999929E+000 ===> 9.4 <- rounded to nearest
9.55000000000000071E+000 ===> 9.6 <- rounded to nearest
9.65000000000000036E+000 ===> 9.7 <- rounded to nearest
9.75000000000000000E+000 ===> 9.8 <- rounded to even
9.84999999999999964E+000 ===> 9.8 <- rounded to nearest
9.94999999999999929E+000 ===> 9.9 <- rounded to nearest
7
Traveller On

The Microsoft documentation have this info carefully hidden in the Standard numeric format strings page (it's probably elsewhere as well, but not in the Double.ToString docs).

Here's the important excerpt, for posterity:

When precision specifier controls the number of fractional digits in the result string, the result string reflects a number that is rounded to a representable result nearest to the infinitely precise result. If there are two equally near representable results:

  • On .NET Framework and .NET Core up to .NET Core 2.0, the runtime selects the result with the greater least significant digit (that is, using MidpointRounding.AwayFromZero).

  • On .NET Core 2.1 and later, the runtime selects the result with an even least significant digit (that is, using MidpointRounding.ToEven).

Since .Net 5 and later mostly continue the Core line despite Microsoft's confusing statements about how they've been merged, that'll pretty clearly fall under the 2nd case.