Why does .NET use a rounding algorithm in String.Format that is inconsistent with the default Math.Round() algorithm?
I开发者_如何学Python've noticed the following inconsistency in C#/.NET. Why is it 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.
As a historical note, the original Visual Basic implementation of Format$ also was inconsistent with round-to-even, aka bankers' 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.
It seems this problem is worse than a "simple" inconsistency:
double dd = 0.034999999999999996;
Math.Round(dd, 2); // 0.03
Math.Round(dd, 2, MidpointRounding.AwayFromZero); // 0.03
Math.Round(dd, 2, MidpointRounding.ToEven); // 0.03
string.Format("{0:N2}", dd); // "0.04"
This is absolutely bananas. Who knows where the heck it gets "0.04" from.
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
WriteLine(string, params object[])
calls string.Format
and passes in the current CultureInfo, so it 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 .NET Reflector, maybe not :)
精彩评论