C# 浮点变量的范围会影响它们的值吗?

C# 浮点变量的范围会影响它们的值吗?,c#,floating-point,C#,Floating Point,如果我们在控制台应用程序上执行以下C#代码,我们将得到一条消息,即总和不相等 static void Main(string[] args) { float f = Sum(0.1f, 0.2f); float g = Sum(0.1f, 0.2f); //System.Console.WriteLine("f = " + f + " and g = " + g); if (f == g) {

如果我们在控制台应用程序上执行以下C#代码,我们将得到一条消息,即总和不相等

    static void Main(string[] args)
    {
        float f = Sum(0.1f, 0.2f);
        float g = Sum(0.1f, 0.2f);

        //System.Console.WriteLine("f = " + f + " and g = " + g);

        if (f == g)
        {
            System.Console.WriteLine("The sums are equal");
        }
        else
        {
            System.Console.WriteLine("The sums are Not equal");
        }
    }

    static float Sum(float a, float b)
    {
        System.Console.WriteLine(a + b);
        return a + b;
    }
如果我们在取消注释行
System.Console.WriteLine()
后执行它,我们将得到一条消息
总和相等

    static void Main(string[] args)
    {
        float f = Sum(0.1f, 0.2f);
        float g = Sum(0.1f, 0.2f);

        //System.Console.WriteLine("f = " + f + " and g = " + g);

        if (f == g)
        {
            System.Console.WriteLine("The sums are equal");
        }
        else
        {
            System.Console.WriteLine("The sums are Not equal");
        }
    }

    static float Sum(float a, float b)
    {
        System.Console.WriteLine(a + b);
        return a + b;
    }
这种行为的真正原因是什么

这种行为的实际原因是什么

我无法提供具体情况的详细信息,但我理解一般问题,以及为什么使用
Console.WriteLine
可以改变事情

正如我们在中看到的,有时对浮点类型执行的操作的精度高于在变量类型中指定的精度。对于局部变量,可以包括在方法执行期间如何将值存储在内存中

我怀疑在你的情况下:

  • Sum
    方法正在内联(但请参阅下文)
  • 求和本身的执行精度高于您预期的32位浮点
  • 其中一个变量(
    f
    say)的值存储在高精度寄存器中
    • 对于该变量,直接存储“更精确”的结果
  • 另一个变量(
    g
    )的值作为32位值存储在堆栈上
    • 对于该变量,“更精确”的结果将减少到32位
  • 执行比较时,堆栈上的变量被提升为更高精度的值,并与其他更高精度的值进行比较,差异是由于其中一个先前丢失了信息,而另一个没有
当您取消注释
Console.WriteLine
语句时,我猜想(出于任何原因)会强制将这两个变量存储在其“适当”的32位精度中,因此它们都被以相同的方式处理

这一假设由于添加

[MethodImpl(MethodImplOptions.NoInlining)]
。。。就我所知,这不会改变结果。不过,我可能在这方面做了些别的错事

实际上,我们应该看看正在执行的汇编代码——不幸的是,我现在没有时间这么做。

(不是真正的答案,但希望有一些支持文档)

配置:Core i7、Windows 8.1、Visual Studio 2013

x86平台:

Version      Optimized Code?        Debugger Enabled?          Outcome
4.5.1        Yes                    No                         Not equal
4.5.1        Yes                    Yes                        Equal
4.5.1        No                     No                         Equal
4.5.1        No                     Yes                        Equal
2.0          Yes                    No                         Not Equal
2.0          Yes                    Yes                        Equal
2.0          No                     No                         Equal
2.0          No                     Yes                        Equal
x64平台:

Version      Optimized Code?        Debugger Enabled?          Outcome
4.5.1        Yes                    No                         Equal
4.5.1        Yes                    Yes                        Equal
4.5.1        No                     No                         Equal
4.5.1        No                     Yes                        Equal
2.0          Yes                    No                         Equal
2.0          Yes                    Yes                        Equal
2.0          No                     No                         Equal
2.0          No                     Yes                        Equal

这种情况似乎只发生在x86配置上的优化代码中。

这与范围无关。它是堆栈动态和浮点处理的结合。对编译器的一些了解将有助于澄清这种违反直觉的行为

注释
Console.WriteLine
时,值
f
g
在求值堆栈上,并一直保留到通过Main方法中的相等性测试为止

如果未注释
Console.Writeline
,则在调用时将值
f
g
从计算堆栈移动到调用堆栈,并在
Console.Writeline
返回时恢复到计算堆栈。如果(f==g)
之后完成,则比较
。在将值存储到调用堆栈的过程中,可能会发生一些舍入,一些信息可能会丢失

在调用
控制台.WriteLine
的场景中,比较测试中的
f
g
不是相同的值。虚拟机已将它们复制并还原为精度和舍入规则不同的格式。

在您的特定代码中,当对
Console.WriteLine
的调用进行注释时,求值堆栈永远不会存储到调用堆栈中,也不会发生舍入。由于允许平台实现在评估堆栈上提供更高的精度,因此可能会出现这种差异

CLI规范允许编辑我们在本例中遇到的内容。第I.12.1.3节内容如下:

浮点数的存储位置(静态、数组元素、, 类的字段)具有固定大小。支持的存储大小 是浮动32和浮动64。其他地方(在求值堆栈上,如 参数(作为返回类型和局部变量)浮点 数字使用内部浮点类型表示。各 在这种情况下,变量或表达式的标称类型为 float32或float64,但其值可以在内部表示 具有额外的范围和/或精度。内部空间的大小 浮点表示依赖于实现,可以变化, 其精度至少应与变量或变量的精度相同 正在表示的表达式

此引文中的关键字是“依赖于实现”和“可以变化”。在OP的案例中,我们看到他的实现确实有所不同


Java平台中的Non-strictfp浮点算法也有一个相关问题,有关更多信息,请查看我对

的回答,我对这一点大体上有所了解,但我不理解为什么取消注释这一行会产生这种效果,因为它对两个变量都做了相同的事情。顺便说一句,它也可能因处理器类型的不同而有所不同。我已经用
csc/o+/debug-/platform:x86 Test.cs
在我的机器构建和运行中重现了这个问题。它可能仍然会因CLR版本等细微的变化而有所不同。这就是为什么Double应该总是与
Math.Abs(a-b)
进行比较,其中epsilon是需要精度的。如果是真的,那么a等于b,具有要求的精度。@RudyVelthuis:你说当给出相同的参数时,结果应该是相同的,这是一种道德陈述。是的,这就是世界应该是什么样子。这不是英特尔给我们的世界。CLR抖动可以选择