c#中点四舍五入选项
本文关键字:选项 四舍五入 | 更新日期: 2023-09-27 18:17:02
虽然我看到了几个关于四舍五入的问题,但我还没有找到答案。在c#/.Net中有没有办法做到中点四舍五入?也就是说,如果一个小数在两个整数的中间,我希望总是四舍五入。据我所知,这是一种常见的舍入方法,所以我很惊讶它没有在标准Math中列出。轮的选择。
87.3 -> 87
87.8 -> 88
87.5 -> 88
-87.3 -> -87
-87.8 -> -88
-87.5 -> -87
我能找到的最接近的是MidpointRounding。AwayFromZero,但这错误地处理负数,因为它们将向下舍入,而不是向上。
如何:
Math.Floor(value + 0.5)
EDIT:上面的代码在思想上是正确的,但是在现实世界中有一些角落的错误。
严格的解决方案是:
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.4999999999999999994表示小于0.5的最大双精度值,即:
1.1111111111111111111111111111111111 112 * 2
加上0.5正好等于:
1.11111111111111111111111111111111111111111111111 2 * 2-1
由于最后1位超过双精度,FPU将其舍入到最接近的偶数[1]:
10.0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 000 2 * 2-1
然后归一化为:
1.0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 000 2 * 20
正好是1.0。
当然,Round(0.49999999999999994)
必须是0,所以在这种特殊情况下,我们只返回0。
另一个幻数4503599627370496.0是252。
大于2且小于2的奇数53加0.5也四舍五入到最接近的偶数,例如4503599627370497.0 + 0.5使:
1.0000000000 0000000000 0000000000 0000000000 0000000000 0112 * 252
并舍入为:
102 * 252
即4503599627370498.0。
也是负数:-4503599627370497.0 + 0.5轮到-4503599627370496.0。
实际上,我们不再需要四舍五入这么大的数字,因为它们已经没有小数部分了。在这种情况下,我们所需要做的就是返回给定的值本身,而不需要任何操作。
参见
- 为什么Math.round(0.4999999999999999994)返回1 Eric Lippert的博客:发现缺陷:舍入,第二部分
[1] CLI spec states:
根据IEC 60559:1989(IEEE 754-1985),IEC 60559:1989中定义的舍入模式应由CLI设置为"舍入到最接近的数字",CIL和类库都没有提供修改此设置的机制。
其中"四舍五入到最接近的数字"表示"四舍五入到最接近的偶数"。
另外,无论是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);
}
其中792281625142643759354395034m接近十进制。MaxValue/10.
绝对值大于0的十进制值不能有小数部分,因此它+ 0.5舍入为偶数。
编辑:这有效,但Ripple的答案要好得多。我将把它留给后人。;)
public static int Round(double value)
{
if ((value > 0) || (int)(value*2)==(value*2))
return (int)(value + 0.5);
else
return (int)(value - 0.5);
}
返回op中所有示例的正确值。
这是我在看到这些答案之前想到的,尽管Ripple的答案要清晰得多:
if (value - Math.Floor(value) == .5m)
{
value = Math.Ceiling(value);
}
else
{
value= Math.Round(value);
}
尽管如此,Math类没有将此作为MidpointRounding的内置选项,这是令人恼火和惊讶的。