怎么样:
Math.Floor(value + 0.5)
编辑:上面的代码在想法上是正确的,但在现实世界中有一些极端情况的错误。
一个严格的解决方案是:
static double Round(double val)
{
// 0.49999999999999994 + 0.5 makes 1.
if (val == 0.49999999999999994)
return 0;
// 4503599627370497.0 + 0.5 makes 4503599627370498.0.
if (val <= -4503599627370496.0 || 4503599627370496.0 <= val)
return val;
return Math.Floor(val + 0.5);
}
那些神奇的数字是什么?
0.49999999999999994代表小于0.5的最大double值,即:
1.1111111111 1111111111 1111111111 1111111111 1111111111 112 * 2-2
加上 0.5 正好:
1.1111111111 1111111111 1111111111 1111111111 1111111111 1112 * 2-1
由于最后 1 位超出双精度,FPU 将其四舍五入到最接近的偶数[1]:
10.0000000000 0000000000 0000000000 0000000000 0000000000 002 * 2-1
然后归一化为:
1.0000000000 0000000000 0000000000 0000000000 0000000000 002 * 20
正好是 1.0。
当然Round(0.49999999999999994) 必须为 0,所以在这种特殊情况下我们只需返回 0。
另一个幻数 4503599627370496.0 是 252。
大于 253 的奇数加上 0.5 舍入到最接近的偶数,例如 4503599627370497.0 + 0.5 使得:
1.0000000000 0000000000 0000000000 0000000000 0000000000 0112 * 252
并四舍五入:
1.0000000000 0000000000 0000000000 0000000000 0000000000 102 * 252
即 4503599627370498.0。
负数也是如此:-4503599627370497.0 + 0.5 轮到 -4503599627370496.0。
实际上我们不再需要对这么大的数字进行四舍五入,因为它们已经没有小数部分了。在这种情况下,我们需要做的就是返回给定值本身,而不需要任何操作。
另请参阅
备注
[1]CLI spec 状态:
IEC 60559:1989 中定义的舍入模式应由 CLI 设置为“舍入到最接近的数字”,CIL 和类库均不提供修改此设置的机制。
根据 IEC 60559:1989(IEEE 754-1985),其中“四舍五入”是指“四舍五入,与偶数相等”。
此外,CIL 和类库都没有提供修改此设置的机制,但我们可以在 Windows 上使用 CRT 函数 _controlfp_s 更改舍入模式,至少在我的环境中: Win7 SP1 64bit, Intel Core i7-2600, .NET 4.0.
下面的实验代码对我来说效果很好:
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int _controlfp_s(IntPtr currentControl, uint newControl, uint mask);
static double RoundWithFPC(double val)
{
// Set the round mode as "Round towards negative infinity."
_controlfp_s(IntPtr.Zero, 0x100 /* _RC_DOWN */, 0x300 /* _MCW_RC */);
double rounded = Math.Floor(val + 0.5);
// Restore the round mode.
_controlfp_s(IntPtr.Zero, 0x000 /* _RC_NEAR */, 0x300 /* _MCW_RC */);
return rounded;
}
除了当参数为 -0.5 时它返回 -0 而不是 0。
那么小数类型呢?它总是可以正常工作,不是吗?
没有。至于十进制类型,正确的方法是:
static decimal Round(decimal val)
{
// 7922816251426433759354395035m + 0.5m makes 7922816251426433759354395036m.
if (val <= -7922816251426433759354395034m || 7922816251426433759354395034m <= val)
return val;
return Math.Floor(val + 0.5m);
}
其中 7922816251426433759354395034m 几乎是十进制。MaxValue / 10。
绝对值大于该值的十进制值不能有小数部分,因此+0.5四舍五入为偶数。