为什么.NET使用银行家的舍入作为默认值?

时间:2008-11-22 19:51:54

标签: .net rounding

根据文档,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。

6 个答案:

答案 0 :(得分:422)

其他答案以及为什么银行家的算法(又名round half to even)是一个不错的选择是非常正确的。与大多数合理分布相比,round half away from zero方法不会产生负偏差或正偏差。

但问题是为什么.NET使用Banker的实际舍入作为默认值 - 答案是微软遵循IEEE 754标准。在备注中的MSDN for Math.Round中也提到了这一点。

另请注意,.NET通过提供MidpointRounding枚举来支持IEEE指定的替代方法。他们当然可以提供more alternatives解决关系,但他们选择只是符合IEEE标准。

答案 1 :(得分:188)

可能是因为它是一种更好的算法。在执行多次舍入的过程中,您将平均所有.5的最终向上和向下舍入。如果您是例如添加一堆舍入数字,则可以更好地估计实际结果。我会说即使它不是人们所期望的,但这可能是更正确的事情。

答案 2 :(得分:86)

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

Math.Round可让您指定MidpointRounding

  • ToEven - 当一个数字介于另外两个数字之间时,它会向最接近的偶数舍入。
  • AwayFromZero - 当一个数字位于另外两个数字之间时,它会向最接近零的数字四舍五入。

答案 3 :(得分:23)

小数主要用于;使用 money 时,银行家的舍入很常见。或者你可以说。

  

大多数银行家都需要   十进制类型;因此确实如此   “银行家的四舍五入”

银行家四舍五入的优势在于,如果您:

平均得到相同的结果
  • 在添加它们之前围绕一组“发票行”,
  • 或将它们添加到总计
  • 之后

加起来之前的舍入在计算机之前的日子里节省了大量的工作。

(在英国,当我们进入十进制银行不会处理半便士,但多年来仍有半便士硬币和商店的价格经常以半便士结束 - 所以很多四舍五入)

答案 4 :(得分:0)

使用Round函数的另一个重载,如下所示:

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

输出 3 。如果你使用

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

你会得到银行家的四舍五入。

答案 5 :(得分:0)

此外,请注意,通过格式字符串(如“ 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