C# 为什么要用这种笨拙的方法将浮点四舍五入为整数?

C# 为什么要用这种笨拙的方法将浮点四舍五入为整数?,c#,floating-point,rounding,C#,Floating Point,Rounding,在中,我们了解到.NET Framework有一种特殊的浮点值舍入方法: public static int DoubleToInt(double val) { return (0 < val) ? (int)(val + 0.5) : (int)(val - 0.5); } 公共静态int-DoubleToInt(双val) { 返回值(0

在中,我们了解到.NET Framework有一种特殊的浮点值舍入方法:

public static int DoubleToInt(double val)
{
    return (0 < val) ? (int)(val + 0.5) : (int)(val - 0.5);
}
公共静态int-DoubleToInt(双val)
{
返回值(0
为什么他们不只是使用
(int)Math.Round(val)


或者:
Math.Round
如果更高级,为什么不这样定义?必须进行一些权衡。

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

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

如果a的分数部分位于两个整数的中间,其中一个是偶数,另一个是奇数,则返回偶数

因此,如果
val==0.5
,那么
Math.Round(val)==0.0
,而这个
DoubleToInt
将给出
(int)(0.5+0.5)==1
。换句话说,
DoubleToInt
round 1/2远离零(就像标准的C
round
函数)


这里还有可能出现不太理想的行为:如果
val
实际上是0.5之前的
double
(即0.4999999994),那么,根据C#处理中间精度的方式,它实际上可能会给出1(因为
val+0.5
不能用
double
表示,可以四舍五入到1)。事实上,这是在Java 6(及更早版本)中实现的。

我认为这是一种优化,因为要从
Round
中获得相同的行为,需要使用
middpointrounding.AwayFromZero
选项。通过以下方式实现:

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;
}
private static unsafe double InternalRound(双值、整数位数、中点舍入模式){
如果(Abs(值)<双圆限值){
Double power10=圆形power10双[位数];
值*=幂10;
如果(mode==中点舍入.AwayFromZero){
双分数=SplitFractionDouble(&value);
如果(Abs(分数)>=0.5d){
值+=符号(分数);
}
}
否则{
//在X86上,这可以内联到几个指令
数值=四舍五入(数值);
}
值/=幂10;
}
返回值;
}

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

因为
Math.Round
有重载来处理要舍入的小数位数和不同的类型。这也是我的猜测。这也会创建一个中间双精度(作为
val+0.5
val-0.5
@SimonByrne,不同之处在于,执行一个常量值的加法比计算出最近的整数要容易得多。这不是创建
double
值,而是计算出这个double值是什么。Hm,硬件应该不支持截断和舍入吗?我不明白为什么纯计算会有成本差异。可能是验证和检测要舍入的数字所导致的成本。@usr硬件可能支持也可能不支持。
round
方法尝试使用本机支持(如果可用),但不支持如果不可用,则计算会更复杂。请注意,通过使用middpointrounding.AwayFromZero选项的重载,您可能会得到相同的行为。我刚刚对变量进行了基准测试:Round(double)是140个单位(但使用了没有人期望的银行家舍入),Round(double,ToEven)是310个单位,DoubleToInt是42个单位和(int)截断为9个单位。基准测试包括将结果添加到累加器中,以确保结果被消耗。因此它的速度要快得多(但不正确)。