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,但这错误地处理负数,因为它们将向下舍入,而不是向上。

c#中点四舍五入选项

如何:

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中定义的舍入模式应由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);
}

其中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的内置选项,这是令人恼火和惊讶的。