为什么.NET默认使用银行家舍入法?

296
根据文档,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的初学者,这可能会产生误导。


116
向上取整并不是“更自然”的方法。自然与此无关,这只是在小学学习“四舍五入”概念时所学到的方法。小学课程并不能始终描绘完整的图景。 - Rob Kennedy
47
@Rob,这就是为什么它更加“自然”,尽管它不是“正确”的原因。 - Pacerier
15
我不明白,@Pacerier。我解释了为什么这不是自然的,你说这实际上就是为什么它是自然的。我的论点如何与你相反的结论产生冲突?你习惯的事物可能会感觉自然,有时我们会比喻地说某些东西是“本能的”,但这并不意味着它们就是自然的。 - Rob Kennedy
16
@Rob,我想说这很自然,因为它感觉很自然。你知道有36个不同的对象都使用了相同的变量名“natural”吗? - Pacerier
12
"自然界肯定是类似物,因此使用这个词是不正确的;但这有点吹毛求疵。也许“通常”是更好的词语……“人们通常会四舍五入到什么程度?”> 0.5 舍入为 1.0." - whytheq
显示剩余8条评论
5个回答

457
其他回答提到银行家舍入法(又称round half to even)是一个好选择的原因是正确的。它在大多数合理的分布中不像round half away from zero方法那样容易出现负面或正面偏差。
但问题是为什么.NET使用银行家实际舍入作为默认值 - 答案是Microsoft遵循了IEEE 754标准。这也在MSDN for Math.Round的备注中提到。
另请注意,.NET通过提供MidpointRounding枚举来支持IEEE指定的替代方法。他们当然可以提供more alternatives来解决平局,但他们选择只满足IEEE标准。

21
为什么IEEE 754采用银行家舍入法?这个(仍然不错的)答案只是在推卸责任。 - H H
5
也许是因为其他答案提到的原因(我进行了总结);它不会受到负面或正面偏见的影响,因此对于大多数分布和问题领域来说,它是一个更合理的默认选择。 - Ostemar
1
@BrandonBarkley Decimal 或者 decimal 是一种浮点数,而且IEEE 754确实包括了十进制浮点数。 - Pablo H
2
@HenkHolterman:或者有人可以争辩说微软把责任推给了别人。 - MestreLion
显示剩余3条评论

208

可能是因为这是一种更好的算法。在执行多次四舍五入后,所有以.5结尾的数字将平均向上和向下四舍五入。如果您例如要添加一堆已经进行了四舍五入的数字,则会得到更好的实际结果估计。我认为即使这不是某些人所期望的,这也是可能更正确的做法。


74
假设您有一个平坦的输入分布,其中奇数和偶数的输入数量相同。 - jk.
7
虽然 Ostemar 已经提供了 真正的 答案(https://dev59.com/GHVC5IYBdhLWcg3wYQEp#6562018),但我支持使用更好的算法,加1。 - Ian Boyd
2
@Ian,我也给那个答案+1。不过我们能不能把“采纳答案移动”一下呢?也许楼主可以做这个操作。关于为什么使用这种方法的实际答案在页面中间。虽然我每周从这个答案中获得了相当大的声望提升,但我还是喜欢它。 - Kibbee
@Kibbee - 感谢您推荐我的答案。我想让原帖发布者自行决定是否更改接受的答案? - Ostemar
@Mr.Z,非常感谢您的帮助! - Rick
显示剩余2条评论

91

虽然我无法回答“为什么微软的设计师选择了这个作为默认值”的问题,但是我想指出一个额外的函数是不必要的。

Math.Round允许你指定一个MidpointRounding

  • ToEven - 当一个数处于两个数中间时,它会被四舍五入到最接近的偶数。
  • AwayFromZero - 当一个数处于两个数中间时,它会被四舍五入到离零更远的那个数。

8
正如我在相关的讨论中提到的那样,请确保你在四舍五入时保持一致性——如果你有时在数据库中进行四舍五入,有时在.NET中进行,你会遇到奇怪的一分钱误差,这将花费你数周的时间来弄清楚。 - chris
62
有一次客户为我支付超过40,000美元的费用,目的是找出两个接近10亿美元但相差$0.11的数字之间的舍入误差;这个$0.11是由于大型机和SQL Server之间第8位数字的舍入误差不同造成的。可以说这是一个完美主义者! - E.J. Brennan
13
如果我在处理十亿美元,我可能会成为一个完美主义者。;-) - royse41
13
@E.J. Brennan: 你花了4万美元才发现这个问题?我经常看到这样的问题,四舍五入是第一原因,双精度/浮点数规范化是第二原因,程序员错误是第三个 - 如果没有预定义的测试案例,第三个可以立即设置为第一。顺便问一下,你能把我介绍给你的亿万富翁客户吗?我觉得我也可以在他的系统中找到更多价值4万美元的错误! :D - Matthieu N.
3
如果你看过《办公室》这部电影,你就会明白。无论何时,当你在金钱交易中发现一些神秘而微小的不准确之处时,解决这些问题几乎总是一个好主意。也许你会决定不修复漏洞,但了解其根本原因仍然有价值。我敢打赌,许多与金钱打交道的人都很高兴注意到即使微小的不准确之处。 - Brian
显示剩余3条评论

22

十进制主要用于货币;在处理货币时,常见的是银行家舍入。或者您可以这样说。

大多数需要使用十进制类型的是银行家,因此它进行“银行家舍入”

银行家舍入的优点在于,如果您:

  • 在将一组“发票行”相加之前进行四舍五入,
  • 或者在将它们相加后进行四舍五入,

平均而言,您将获得相同的结果。

在计算机出现之前,先进行四舍五入再相加可以节省很多工作。

(在英国转为十进制后,银行不会处理半便士,但在很多年中,仍然有半便士硬币,商店的价格通常以半便士结尾 - 因此需要进行很多四舍五入)


8
小数通常用于货币和其他非整数的情况。 - John Tyree
3
大多数情况下,当不是整数时,会使用双精度/浮点数。请参阅https://dev59.com/83E85IYBdhLWcg3w64EA - Ian Ringrose
4
哇,我犯了一个可笑的错误。应该是“Decimals”的拼写,而不是“Decmial”。为了后人,我同意原来的观点。 - John Tyree
银行家可能喜欢银行家舍入法,但簿记员可能不喜欢它。他们说,如果0.005的差异应该四舍五入到0.01,而不是取决于它是奇数还是偶数。 - JustAMartin

0

使用另一个Round函数的重载,像这样:

decimal.Round(2.5m, 0,MidpointRounding.AwayFromZero)

它将输出3。如果您使用

decimal.Round(2.5m, 0,MidpointRounding.ToEven)

你将获得银行家舍入。


6
这并没有回答为什么选择“银行家舍入法”作为默认选项的问题。 - shortstuffsushi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接