浮点文字与浮点变量的奇怪编译器行为 [英] Strange compiler behavior with float literals vs float variables
问题描述
我注意到C#编译器对float舍入/截断有一个有趣的行为。即,当float文字超出保证的可表示范围(7个十进制数字)时,a)将float结果显式转换为float(从语义上讲是不必要的操作),b)将中间计算结果存储在局部变量中都会改变输出。例如:
I have noticed an interesting behavior with float rounding / truncation by the C# compiler. Namely, when a float literal is beyond the guaranteed representable range (7 decimal digits), then a) explicitly casting a float result to float (a semantically unnecessary operation) and b) storing intermediate calculation results in a local variable both change the output. An example:
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);
}
}
输出为:
205
204
205
205
205
在计算机上的JITted调试版本中,b的计算方式如下:
In the JITted debug build on my computer, b is calculated as follows:
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
而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
最后,我的问题:为什么输出的第二行与第四行不同吗?那多余的fmul会带来不同吗?还要注意,如果删除或什至减少了浮点数f的最后一个数字(已经无法表示),则所有内容都会落在原处。
Finally, my question: why is the second line of the output different from the fourth? Does that extra fmul make such a difference? Also note that if the last (already unrepresentable) digit from the float f is removed or even reduced, everything "falls in place".
推荐答案
您的问题可以简化为询问为什么这两个结果不同:
Your question can be simplified to asking why these two results are different:
float f = 2.0499999f;
var a = f * 100f;
var b = (int)(f * 100f);
var d = (int)a;
Console.WriteLine(b);
Console.WriteLine(d);
如果您查看.NET Reflector中的代码,您会发现上面的代码实际上被编译为如果是以下代码:
If you look at the code in .NET Reflector you can see that the above code is actually compiled as if it were the following code:
float f = 2.05f;
float a = f * 100f;
int b = (int) (f * 100f);
int d = (int) a;
Console.WriteLine(b);
Console.WriteLine(d);
不能始终精确地计算浮点数。 2.05 * 100f
的结果不完全等于205,但由于舍入误差而略少。当此中间结果转换为整数时,将被截断。作为浮点数存储时,将四舍五入为最接近的可表示形式。这两种舍入方法给出不同的结果。
Floating point calculations cannot always be made exactly. The result of 2.05 * 100f
is not exactly equal to 205, but just a little less due to rounding errors. When this intermediate result is converted to an integer is truncated. When stored as a float it is rounded to the nearest representable form. These two methods of rounding give different results.
关于您在撰写本文时对我的答案的评论:
Regarding your comment to my answer when you write this:
Console.WriteLine((int) (2.0499999f * 100f));
Console.WriteLine((int)(float)(2.0499999f * 100f));
计算完全在编译器中完成。上面的代码等效于此:
The calculations are done entirely in the compiler. The above code is equivalent to this:
Console.WriteLine(204);
Console.WriteLine(205);
这篇关于浮点文字与浮点变量的奇怪编译器行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!