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

Why use this awkward way to round a float to an integer?

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 不这样定义?必须有所取舍。

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

它们在小数部分 1/2 的值上有不同的行为。根据 Math.Round:

If the fractional component of a is halfway between two integers, one of which is even and the other odd, then the even number is returned.

所以如果 val == 0.5,那么 Math.Round(val) == 0.0,而这个 DoubleToInt 会给出 (int)(0.5+0.5) == 1。换句话说,DoubleToInt 从零开始 1/2 舍入(就像标准 C round 函数)。

这里也有可能出现不太理想的行为:如果 val 实际上是 double before 0.5(即 0.49999999999999994),则取决于如何C# 处理中间精度,它实际上可能给出 1(因为 val + 0.5 不能用 double 表示,可以四舍五入为 1)。这实际上是 Java 6(及更早版本)中的 an infamous specification bug

我认为这是一种优化,因为要从 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;
}

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