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中的最后一个(已经无法代表的)数字被删除或甚至减少,则所有内容都"落实到位".
您的问题可以简化为询问为什么这两个结果不同:
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)