【问题标题】:C# rounding differently depending on platform?C# 舍入取决于平台?
【发布时间】:2017-11-15 08:37:38
【问题描述】:

我有一小段代码

double s = -2.6114289999999998;
double s7 = Math.Round(s, 7);
double s5 = Math.Round(s, 5);
double s6 = Math.Round(s, 6);

使用 Platform = Any CPU,我得到

s7: -2.611429   
s5: -2.61143    
s6: -2.611429   

使用 Platform = x64,我得到

s7: -2.6114289999999998 
s5: -2.61143    
s6: -2.6114289999999998

为什么? (从 VS 的 Locals 窗口复制的输出)


整段代码是:

    private void btnAlign_Click(object sender, EventArgs e)
    {
        double s = -2.6114289999999998;
        double s7 = Math.Round(s, 7);
        double s5 = Math.Round(s, 5);
        double s6 = Math.Round(s, 6);
    }

【问题讨论】:

  • 你能贴出整个代码来重现吗?
  • 我在找一位同事 - 他遇到了它,我已经在自己的电脑上进行了审查。
  • 在 VS 15.3.3 和 .NET 4.5.2 中确认。
  • 如果你使用 Jon Skeet 的DoubleConverters6s7 在 32 位和 64 位中是完全相同的值(与 s 相同)。但是它们在调试器本地窗口中的显示方式不同。我会说调试器的演示比运行时更成问题。

标签: c# rounding


【解决方案1】:

-2.611429 无法使用 64 位浮点数表示。在 32 位模式下编译时,该值将改为使用 80 位 (extended precision)。

【讨论】:

  • 不知道 c# 折叠双打超过 64 位。谢谢。
  • 具体来说,在 x64 上使用 SSE2 FPU,在 x86 上使用 x87 FPU。
  • -2.611429 不能用二进制浮点句点精确表示,无论精度如何。结果是没有有限数量的位可以表示的连分数。精度的差异只会在舍入(和解析字符串)时导致不同的结果,但 80 位精度在某种程度上就足够了(从这个答案中可以推断出)是不正确的。
【解决方案2】:

On x64, the SSE2 FPU is used and on x86 the x87 FPU is used.

可以(尽管不建议)将 x87 精度更改为与 SSE2 精度相同(即使用较低的精度)。

您可以通过_controlfp() API 做到这一点。

以下程序演示。在 x86 模式下运行此程序,您将看到使用 _controlfp(0x00030000, 0x00020000) 会导致输出更改为与 x64 版本相似(但不完全相同!):

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp3
{
    class Program
    {
        static void Main()
        {
            double s = -2.6114289999999998;

            Console.WriteLine(Math.Round(s, 7).ToString("R")); // -2.611429

            if (!Environment.Is64BitProcess)
                _controlfp(0x00030000, 0x00020000);

            Console.WriteLine(Math.Round(s, 7).ToString("R")); // -2.61142897605896
        }

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern uint _controlfp(uint newcw, uint mask);
    }
}

但是,您不应该以这种方式弄乱 FPU(如果这样做,您应该尽快恢复到以前的设置)。

【讨论】:

  • 哇,看起来可能需要大量的调试时间:)
【解决方案3】:

根据 CSharp 语言规范 3.0,可能会出现以下情况:

有关详细信息,请参阅规范中的第 4.1.6 章

浮点运算可以以比运算结果类型更高的精度执行。例如,一些硬件架构支持比 double 类型具有更大范围和精度的“扩展”或“long double”浮点类型,并使用这种更高精度类型隐式执行所有浮点运算。只有在性能成本过高的情况下,才能使此类硬件架构以较低的精度执行浮点运算,而不是要求实现同时丧失性能和精度,C# 允许将更高精度的类型用于所有浮点运算.除了提供更精确的结果之外,这很少有任何可衡量的影响。然而,在 x * y / z 形式的表达式中,乘法产生的结果超出了双精度范围,但随后的除法将临时结果带回双精度范围,事实上,表达式在更高的范围内计算范围格式可能会导致产生有限结果而不是无穷大。

简而言之:C# 规范实际上指出硬件架构可能会对浮点类型(Double、Float)产生一些影响。

The language specification as .doc can be found here.

【讨论】:

    【解决方案4】:

    在这里回答:https://stackoverflow.com/a/19978623/4891523

    x64 托管代码将使用 SSE 进行双精度/浮点计算,而不是 使用 x86 托管代码时的 x87 FPU。

    【讨论】:

    • x87 的精度更高,那么这如何解释上述情况呢?另外,如果 Math.Round() 未能执行所需的操作,调用它有什么意义?
    • 如果您认为这是答案,那么正确的回答是将其标记为重复。也就是说,我认为这并不能真正回答问题。它提出了一个有趣的(可能相关的)观点,但没有解释与问题的相关性或这句话的含义。对我来说,这不是骗局,但这也不是答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-10
    相关资源
    最近更新 更多