在c#中舍入中点舍入选项

时间:2014-08-07 15:00:05

标签: c# .net math rounding

虽然我已经看过几个关于舍入的问题,但我还没有找到答案。在C#/ .Net中有没有办法进行中点舍入?也就是说,如果小数位于2个整数之间,我想总是向上舍入。据我所知,这是一种常见的舍入方法,所以我很惊讶它没有列在标准的Math.Round选项中。

87.3 -> 87
87.8 -> 88
87.5 -> 88
-87.3 -> -87
-87.8 -> -88
-87.5 -> -87

我能找到的最接近的是MidpointRounding.AwayFromZero,但这会错误地处理负片,因为它们会向下舍入而不是向上。

3 个答案:

答案 0 :(得分:8)

怎么样:

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的最大双值,即:
1.1111111111 1111111111 1111111111 1111111111 1111111111 11 2 * 2 -2
并且添加0.5确切地说: 1.1111111111 1111111111 1111111111 1111111111 1111111111 111 2 * 2 -1

由于最后1位超过双精度,FPU将其舍入到最近的偶数 [1]
10.0000000000 0000000000 0000000000 0000000000 0000000000 00 2 * 2 -1
然后归一化为:
1.0000000000 0000000000 0000000000 0000000000 0000000000 00 2 * 2 0
这正好是1.0 当然Round(0.49999999999999994)必须为0,所以我们在这种特殊情况下只返回0。

另一个幻数4503599627370496.0是2 52 奇数大于此且小于2 53 加上0.5轮甚至最接近,例如4503599627370497.0 + 0.5使得:
1.0000000000 0000000000 0000000000 0000000000 0000000000 011 2 * 2 52
和轮到:
1.0000000000 0000000000 0000000000 0000000000 0000000000 10 2 * 2 52
那是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 64位,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舍入。

答案 1 :(得分:1)

编辑:这很有效,但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中所有示例的正确值。

答案 2 :(得分:0)

这是我在看到这些答案之前想出来的,尽管Ripple的回答更清晰:

if (value - Math.Floor(value) == .5m)
{
    value = Math.Ceiling(value);
}
else
{
    value= Math.Round(value);
}

即便如此,Math类并没有将它作为MidpointRounding的选项内置而烦人且令人惊讶。