views:

405

answers:

3

I've noticed the following inconsistency in C#/.NET. I was wondering why it is so.

Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.04, Math.Round(1.04, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.05, Math.Round(1.05, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.06, Math.Round(1.06, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.14, Math.Round(1.14, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.15, Math.Round(1.15, 1));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.16, Math.Round(1.16, 1));
Console.WriteLine();
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.04, Math.Round(1.04, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.05, Math.Round(1.05, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.06, Math.Round(1.06, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.14, Math.Round(1.14, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.15, Math.Round(1.15, 1, MidpointRounding.AwayFromZero));
Console.WriteLine("{0,-4:#.0} | {1,-4:#.0}", 1.16, Math.Round(1.16, 1, MidpointRounding.AwayFromZero));

Output:

1.0  | 1.0
1.1  | 1.0
1.1  | 1.1
1.1  | 1.1
1.2  | 1.2
1.2  | 1.2

1.0  | 1.0
1.1  | 1.1
1.1  | 1.1
1.1  | 1.1
1.2  | 1.2
1.2  | 1.2

It appears that the default string formatting behaviour is to round using MidpointRounding.AwayFromZero rather than Math.Round()'s default of MidpointRounding.ToEven.

+1  A: 

Please, take a look here: Possible Bug: Math.Round returning inconsistent results

WriteLine() simply calls Object.ToString(), which in turn ultimately results in a call to Number.FormatDouble(this, null, NumberFormatInfo.CurrentInfo). As you can see, the parameter for the format string is null. If you want to get the real thing from ToString() you must use System.Diagnostics.Debug.WriteLine(n.ToString("R")).

"When a Single or Double value is formatted using this specifier, it is first tested using the general format, with 15 digits of precision for a Double and 7 digits of precision for a Single. If the value is successfully parsed back to the same numeric value, it is formatted using the general format specifier. If the value is not successfully parsed back to the same numeric value, it is formatted using 17 digits of precision for a Double and 9 digits of precision for a Single." Standard Numeric Format Strings

Rubens Farias
just beat me to it!
Mitch Wheat
I don't think that this problem has anything to do with double precision or round-triping. I just think String.Format() is rounding using MidpointRounding.AwayFromZero.
orj
A: 

WriteLine(string, params object[]) calls string.Format and passes in the current CultureInfo, so will use your localized NumberFormatInfo to determine how to write the number. Math.Round does not take culture into account, since you are specifying exactly how you want it to round.

Hm, after poking around Reflector, maybe not :)

Rex M
I can't find anywhere in the docs for CultureInfo where it defines the rounding algorithm used when formatting numbers.
orj
+2  A: 

As a historical note, the original Visual Basic implementation of Format$ also was inconsistent with round-to-even, aka, Banker's Rounding. The original Format$ code was written by Tim Paterson. You might recall that Tim was the author of a little program called QDOS (later known as MS-DOS) that was rather a good seller for a while there.

Perhaps this is yet another case of 25 years of backwards compatibility.

Eric Lippert
Would be nice to discover C# have VB legacy code =)
Rubens Farias