为什么.Net在String.Format中使用与默认Math.Round()算法不一致的舍入算法?

时间:2010-02-09 01:12:50

标签: c# .net math rounding string-formatting

我注意到C#/ .NET中存在以下不一致之处。我想知道为什么会这样。

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));

输出:

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

看来默认的字符串格式化行为是使用MidpointRounding.AwayFromZero而不是Math.Round()的默认值MidpointRounding.ToEven进行舍入。

4 个答案:

答案 0 :(得分:12)

作为一个历史记录,Format $的原始Visual Basic实现也与round-to-even,又名Banker's Rounding不一致。最初的Format $代码由Tim Paterson编写。你可能还记得蒂姆是一个名为QDOS(后来称为MS-DOS)的小程序的作者,这个程序在一段时间内是一个很好的卖家。

也许这是另一个25年向后兼容的案例。

答案 1 :(得分:3)

似乎这个问题比“简单”的不一致更糟糕:

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"

这绝对是香蕉。谁知道它从哪里得到“0.04”。

答案 2 :(得分:2)

请看一下:Possible Bug: Math.Round returning inconsistent results

  

WriteLine()只调用Object.ToString(),最终导致调用Number.FormatDouble(this, null ,NumberFormatInfo.CurrentInfo)。如您所见,格式字符串的参数为null。如果你想从ToString()获得真实的东西,你必须使用System.Diagnostics.Debug.WriteLine(n.ToString(“R”))。

     

“当使用此说明符格式化Single或Double值时,首先使用通用格式对其进行测试,对于Double,精度为15位,精度为7位精度。如果值成功解析为相同的数值,使用通用格式说明符进行格式化。如果该值未成功解析回相同的数值,则使用17位精度格式化为Double,将9位精度格式化为Single。“ Standard Numeric Format Strings

答案 3 :(得分:0)

WriteLine(string, params object[])调用string.Format并传入当前的CultureInfo,因此将使用您的本地化NumberFormatInfo来确定如何编写该数字。 Math.Round不会考虑文化因素,因为您确切地指定了它想要的方式。

嗯,在窥探Reflector之后,也许不是:)