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

Jog*_*rge 61 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;
    }
Run Code Online (Sandbox Code Playgroud)

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

Mis*_*hax 46

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

Console.WriteLine评论时,值f和值g在评估堆栈上并保持在那里,直到您在Main方法中通过相等测试.

Console.Writeline没有被注释掉,价值观f以及g在调用的瞬间从计算堆栈调用堆栈移动,恢复到计算堆栈时Console.WriteLine返回.然后你的比较if (f == g)就完成了.在将值存储到调用堆栈期间可能发生一些舍入,并且一些信息可能丢失.

在你调用的场景Console.WriteLine中,fg在对比测试是不一样的价值观.它们已被复制并恢复为虚拟机具有不同精度和舍入规则的格式.

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

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

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

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

Java平台中的非严格浮点运算也存在相关问题,对于更多信息检查,我对JVM上的Will浮点运算的回答在所有平台上都给出了相同的结果吗?

  • 但即便如此,即使它们被存储和存储回来,即使发生这种情况时发生路由,因为它们*正好是*相同的值,所以舍入值应该仍然相同,对吧?或者C#中的舍入是否有随机元素? (3认同)
  • @Rudy基本上x86使用80位内部格式(这是一件好事^ TM)因此意味着它实际上相当昂贵*并且*精度不好迫使您将所有中间结果保持为IEEE格式.对于那些基本上已经破坏的东西(不要比较花车的等价性而且你很好).即使在最好的情况下,浮点运算也是违反直觉的,或多或少真的没有区别. (2认同)

Jon*_*eet 24

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

我无法详细说明这个特定情况下究竟发生了什么,但我理解一般问题,以及为什么使用Console.WriteLine可以改变一些事情.

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

我怀疑你的情况:

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

当您取消注释该Console.WriteLine语句时,我猜测(无论出于何种原因)强制将两个变量存储在它们的"正确"32位精度中,因此它们都以相同的方式处理.

这个假设在某种程度上被加入的事实搞砸了

[MethodImpl(MethodImplOptions.NoInlining)]
Run Code Online (Sandbox Code Playgroud)

......就我所见,并没有改变结果.我可能会在这些方面做错其他的事情.

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

  • David Monniaux写了一篇关于为什么会发生这种和更多阴险行为的文章.它是在C的上下文中,反映了当时C编译器的标准符合状态(情况有所改善),但我认为在这个问题的上下文中它仍然是一个很好的阅读:http:// arxiv.org/abs/cs/0701192 (2认同)

Bas*_*Bas 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
Run Code Online (Sandbox Code Playgroud)

平台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
Run Code Online (Sandbox Code Playgroud)

只有x86配置上的优化代码才会出现这种情况.