为什么要使用这种笨拙的方式将浮点数舍入为整数?
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;
}
只能猜测实用方法的作者做了一些性能比较
在 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;
}
只能猜测实用方法的作者做了一些性能比较