浮点文字与浮点变量的奇怪编译器行为

Ala*_*lan 7 c# compiler-construction floating-point

我注意到C#编译器浮动舍入/截断有趣的行为.也就是说,当浮点字面值超出保证的可表示范围(7个十进制数字)时,则a)显式地将float结果转换为float(语义上不必要的操作)和b)将中间计算结果存储在局部变量中都会改变输出.一个例子:

using System;

class Program
{
    static void Main()
    {
        float f = 2.0499999f;
        var a = f * 100f;
        var b = (int) (f * 100f);
        var c = (int) (float) (f * 100f);
        var d = (int) a;
        var e = (int) (float) a;
        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.WriteLine(c);
        Console.WriteLine(d);
        Console.WriteLine(e);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

205
204
205
205
205
Run Code Online (Sandbox Code Playgroud)

在我的计算机上的JITted调试版本中,b的计算方法如下:

          var b = (int) (f * 100f);
0000005a  fld         dword ptr [ebp-3Ch] 
0000005d  fmul        dword ptr ds:[035E1648h] 
00000063  fstp        qword ptr [ebp-5Ch] 
00000066  movsd       xmm0,mmword ptr [ebp-5Ch] 
0000006b  cvttsd2si   eax,xmm0 
0000006f  mov         dword ptr [ebp-44h],eax 
Run Code Online (Sandbox Code Playgroud)

而d的计算方法是

          var d = (int) a;
00000096  fld         dword ptr [ebp-40h] 
00000099  fstp        qword ptr [ebp-5Ch] 
0000009c  movsd       xmm0,mmword ptr [ebp-5Ch] 
000000a1  cvttsd2si   eax,xmm0 
000000a5  mov         dword ptr [ebp-4Ch],eax 
Run Code Online (Sandbox Code Playgroud)

最后,我的问题是:为什么输出的第二行与第四行不同?额外的fmul会产生这样的差异吗?另请注意,如果浮动f中的最后一个(已经无法代表的)数字被删除或甚至减少,则所有内容都"落实到位".

Mar*_*ers 5

您的问题可以简化为询问为什么这两个结果不同:

float f = 2.0499999f;
var a = f * 100f;
var b = (int)(f * 100f);
var d = (int)a;
Console.WriteLine(b);
Console.WriteLine(d);
Run Code Online (Sandbox Code Playgroud)

如果查看.NET Reflector中的代码,您可以看到上面的代码实际编译好像是以下代码:

float f = 2.05f;
float a = f * 100f;
int b = (int) (f * 100f);
int d = (int) a;
Console.WriteLine(b);
Console.WriteLine(d);
Run Code Online (Sandbox Code Playgroud)

不能总是精确地进行浮点计算.结果2.05 * 100f不完全等于205,但由于舍入误差而略微减少.当此中间结果转换为整数时,将被截断.当存储为浮点数时,它会舍入到最接近的可表示形式.这两种舍入方法给出了不同的结果.


关于你在写这篇文章时对我的答案的评论:

Console.WriteLine((int) (2.0499999f * 100f));
Console.WriteLine((int)(float)(2.0499999f * 100f));
Run Code Online (Sandbox Code Playgroud)

计算完全在编译器中完成.上面的代码相当于:

Console.WriteLine(204);
Console.WriteLine(205);
Run Code Online (Sandbox Code Playgroud)