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

Gen*_*ari 2 .net c# 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
Run Code Online (Sandbox Code Playgroud)

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

Rip*_*ple 8

怎么样:

Math.Floor(value + 0.5)
Run Code Online (Sandbox Code Playgroud)

编辑:上面的代码是正确的想法,但在现实世界中有一些角落案例错误.

严格的解决方案是:

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);
}
Run Code Online (Sandbox Code Playgroud)

那些神奇的数字是什么?

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规范说明:

CLI 60559:1989中定义的舍入模式应由CLI设置为"舍入到最接近的数字",CIL和类库都不提供修改此设置的机制.

根据IEC 60559:1989(IEEE 754-1985),"舍入到最接近的数字"意味着"舍入到最近,连接到偶数" .

此外,CIL和类库都没有提供修改此设置的机制,但我们可以_controlfp_s在Windows上使用CRT功能更改舍入模式,至少在我的环境中: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;
}
Run Code Online (Sandbox Code Playgroud)

除了当参数为-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);
}
Run Code Online (Sandbox Code Playgroud)

其中7922816251426433759354395034m几乎是
十进制.MaxValue/10. 一个十进制值,其绝对值大于该值不能有小数部分,因此它+ 0.5舍入为偶数.