为什么使用这种笨拙的方式将浮点数舍入为整数?

时间:2016-09-13 19:39:32

标签: c# floating-point rounding

How does DoubleUtil.DoubleToInt(double val) work?中我们了解到.NET Framework有一种舍入浮点值的特殊方法:

public static int DoubleToInt(double val)
{
    return (0 < val) ? (int)(val + 0.5) : (int)(val - 0.5);
}

为什么他们不只是使用(int)Math.Round(val)

或者:如果这是优越的,为什么Math.Round没有这样定义?必须有一些权衡。

3 个答案:

答案 0 :(得分:4)

Math.Round会导致创建具有所需精确值的double,然后需要将其转换为int。这里的代码避免了double的创建。它还允许省略错误处理,并将与其他类型的舍入模式或数字相关的代码舍入到。

答案 1 :(得分:3)

它们在值上具有不同的行为,其中小数部分为1/2。根据{{​​3}}:

  

如果a的小数分量在两个整数之间,其中一个是偶数,另一个是奇数,则返回偶数。

因此,如果val == 0.5,则为Math.Round(val) == 0.0,而此DoubleToInt会给(int)(0.5+0.5) == 1。换句话说,DoubleToInt距离零点1/2(与标准C round函数一样)。

此处也存在不太理想的行为:如果val实际上是 0.5之前的double (即0.49999999999999994),那么,取决于C#如何处理中间精度,它实际上可能给1(因为val + 0.5不能由double表示,并且可以舍入为1)。事实上,这在Java 6(及更早版本)中是Math.Round

答案 2 :(得分:1)

我可以看到这是一种优化,因为要从Round获得相同的行为,您需要使用MidpointRounding.AwayFromZero选项。从reference source开始,这是通过以下方式实现的:

private static unsafe double InternalRound(double value, int digits, MidpointRounding mode) {
    if (Abs(value) < doubleRoundLimit) {
        Double power10 = roundPower10Double[digits];
        value *= power10;
        if (mode == MidpointRounding.AwayFromZero) {                
            double fraction = SplitFractionDouble(&value); 
            if (Abs(fraction) >= 0.5d) {
                value += Sign(fraction);
            }
        }
        else {
            // On X86 this can be inlined to just a few instructions
            value = Round(value);
        }
        value /= power10;
    }
    return value;
}

我只能猜测实用程序方法的作者做了一些性能比较。