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

我注意到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进行舍入。

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

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

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

 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”。

请看一下: 可能的错误:Math.Round返回不一致的结果

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位数。 标准数字格式字符串

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

嗯,在reflection器周围戳,也许不是:)