题
根据文档, decimal.Round
方法使用舍入到偶数算法,这对于大多数应用程序来说并不常见。因此,我总是最终编写一个自定义函数来执行更自然的四舍五入算法:
public static decimal RoundHalfUp(this decimal d, int decimals)
{
if (decimals < 0)
{
throw new ArgumentException("The decimals must be non-negative",
"decimals");
}
decimal multiplier = (decimal)Math.Pow(10, decimals);
decimal number = d * multiplier;
if (decimal.Truncate(number) < number)
{
number += 0.5m;
}
return decimal.Round(number) / multiplier;
}
有人知道这个框架设计决策背后的原因吗?
框架中是否有内置的上舍入算法实现?或者也许是一些非托管的 Windows API?
对于初学者来说,简单地写可能会产生误导 decimal.Round(2.5m, 0)
期望结果为 3,但得到的却是 2。
解决方案
可能是因为它是一种更好的算法。在执行多次舍入的过程中,您将平均所有.5的最终向上和向下舍入。如果您是例如添加一堆舍入数字,则可以更好地估计实际结果。我会说即使它不是人们所期望的,但这可能是更正确的事情。
其他提示
其他答案解释了为什么银行家的算法(又名将一半转为偶数)是一个很好的选择是非常正确的。它没有像离零一半的方法那样遭受负面或正面的偏见超过最合理的分配。
但问题是为什么.NET使用Banker的实际舍入作为默认值 - 答案是微软已经遵循 IEEE 754 标准。 MSDN for Math.Round 中也提到了这一点。备注
另请注意,.NET通过提供MidpointRounding
枚举来支持IEEE指定的替代方法。他们当然可以提供更多替代方案来解决关系,但他们选择只是符合IEEE标准。
虽然我无法回答“为什么微软的设计者选择这个作为默认值?”的问题,但我只想指出额外的功能是不必要的。
Math.Round
允许您指定一个 MidpointRounding
:
- ToEven - 当一个数字介于另外两个数字之间时,它会向最接近的偶数舍入。
- AwayFromZero - 当一个数字介于另外两个数字之间时,它会向远离零的最接近的数字舍入。
小数主要用于 钱;与银行家合作时,四舍五入很常见 钱. 。或者你可以说。
大多数是银行家需要小数类型。因此,它确实是“银行家的四舍五入”
银行四舍五入的优点是,如果您满足以下条件,平均而言您将获得相同的结果:
- 在将一组“发票行”相加之前对其进行四舍五入,
- 或将它们相加然后将总数四舍五入
在计算机出现之前,先舍入再相加可以节省大量工作。
(在英国,当我们采用十进制时,银行不会处理半便士,但多年来仍然有半便士硬币,商店经常有以半便士结尾的价格 - 所以有很多四舍五入)
使用Round函数的另一个重载,如下所示:
decimal.Round(2.5m, 0,MidpointRounding.AwayFromZero)
输出 3 。如果你使用
decimal.Round(2.5m, 0,MidpointRounding.ToEven)
你会得到银行家的四舍五入。
另请注意<!>“;舍入<!>”;通过格式字符串(如<!>“; 0 <!>”;)产生的结果与<!>“Math.Round()<!>”;不同。也就是说,5,.5等等总是被四舍五入:
let d, d' = 2.5, 3.5
Debug.WriteLine(Math.Round(d)) // 2.5 -> 2
Debug.WriteLine(d.ToString("0")) // 2.5 -> 3
Debug.WriteLine(Math.Round(d')) // 3.5 -> 4
Debug.WriteLine(d'.ToString("0")) // 3.5 -> 4
let dd, dd' = 2.25, 2.35
Debug.WriteLine(Math.Round(dd, 1)) // 2.25 -> 2.2
Debug.WriteLine(dd.ToString("0.0")) // 2.25 -> 2.3
Debug.WriteLine(Math.Round(dd', 1)) // 2.35 -> 2.4
Debug.WriteLine(dd'.ToString("0.0")) // 2.35 -> 2.4