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

时间:2014-06-20 06:31:13

标签: c# floating-point

如果我们在控制台应用程序上执行以下C#代码,我们将收到The sums are Not equal的消息。

如果我们在取消注释行System.Console.WriteLine()后执行它,我们会收到The sums are equal的消息。

    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;
    }

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

3 个答案:

答案 0 :(得分:46)

与范围无关。它是堆栈动态和浮点处理的组合。编译器的一些知识将有助于明确这种违反直觉的行为。

评论Console.WriteLine时,值fg位于评估堆栈中并保持不变,直到您在Main方法中通过了相等性测试。

如果未注释Console.Writeline,则会在调用时将值fg从评估堆栈移动到调用堆栈,以还原到评估堆栈当Console.WriteLine返回时。然后你的比较if (f == g)就完成了。在将值存储到调用堆栈期间可能发生一些舍入,并且一些信息可能丢失。

在您调用Console.WriteLine的方案中,比较测试中的fg值不同。它们已被复制并恢复为虚拟机对精度和舍入有不同规则的格式。

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

EDIT CLI规范允许我们在这种情况下遇到的问题。在第I.12.1.3节中,它的内容为:

  

浮点数的存储位置(静态,数组元素,   和类的领域)是固定的大小。支持的存储大小   是float32和float64。其他地方(在评估堆栈上,作为   参数,返回类型和局部变量)浮点数   数字使用内部浮点类型表示。每个   例如,变量或表达式的名义类型是   float32或float64,但其值可以在内部表示   具有额外的范围和/或精度。内部的大小   浮点表示是依赖于实现的,可以变化,   并且应具有至少与变量或变量一样大的精度   表达被表达。

此引文中的关键字是“依赖于实现”和“可能会有所不同”。在OP的情况下,我们看到他的实现确实有所不同。

Java平台中的非严格浮点运算也存在相关问题,有关更多信息,请查看我对Will floating point operations on the JVM give the same results on all platforms?的回答

答案 1 :(得分:24)

  

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

我无法详细说明此特定情况下发生了什么,但我了解一般问题,以及使用Console.WriteLine进行更改的原因

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

我怀疑你的情况:

  • 正在内联Sum方法(但稍后会见)
  • 总和本身的执行精度高于您期望的32位浮点数
  • 一个变量(f说)的值存储在高精度寄存器中
    • 对于这个变量,"更精确"结果直接存储
  • 其他变量(g)的值作为32位值存储在堆栈中
    • 对于这个变量,"更精确"结果被减少到32位
  • 执行比较时,堆栈上的变量升级到更高精度的值并与其他更高精度的值进行比较,差异是由于其中一个变量以前丢失了信息而另一个没有

当您取消注释Console.WriteLine语句时,我猜测(无论出于何种原因)迫使两个变量存储在他们的"正确的" 32位精度,所以它们都以同样的方式处理。

添加

这一事实有点搞砸了这个假设
[MethodImpl(MethodImplOptions.NoInlining)]

... 根据我的意见改变结果。我可能会在这些方面做错其他的事情。

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

答案 2 :(得分:14)

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

配置: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配置上的优化代码才会出现这种情况。