C#Float表达式:将结果float转换为int时的奇怪行为


128

我有以下简单代码:

int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;

speed1并且speed2应该具有相同的值,但实际上,我有:

speed1 = 61
speed2 = 62

我知道我应该使用Math.Round而不是强制转换,但是我想了解为什么值不同。

我查看了生成的字节码,但是除了存储和加载之外,操作码是相同的。

我也在Java中尝试了相同的代码,并且正确获得了62和62。

有人可以解释吗?

编辑: 在实际代码中,它不是直接6.2f * 10,而是函数调用*常量。我有以下字节码:

speed1

IL_01b3:  ldloc.s    V_8
IL_01b5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ba:  ldc.r4     10.
IL_01bf:  mul
IL_01c0:  conv.i4
IL_01c1:  stloc.s    V_9

speed2

IL_01c3:  ldloc.s    V_8
IL_01c5:  callvirt   instance float32 myPackage.MyClass::getSpeed()
IL_01ca:  ldc.r4     10.
IL_01cf:  mul
IL_01d0:  stloc.s    V_10
IL_01d2:  ldloc.s    V_10
IL_01d4:  conv.i4
IL_01d5:  stloc.s    V_11

我们可以看到操作数是浮点数,唯一的区别是stloc/ldloc

至于虚拟机,我尝试了Mono / Win7,Mono / MacOS和.NET / Windows,结果相同。


9
我的猜测是,其中一项操作是单精度完成的,而另一项则是双精度完成的。其中一个返回的值略小于62,因此在截断为整数时会产生61。
加布

2
这些是典型的浮点精度问题。
TJHeuvel 2012年

3
在.Net / WinXP,.Net / Win7,Mono / Ubuntu和Mono / OSX上尝试此操作,将得到两个Windows版本的结果,但在两个Mono版本中,speed1和speed2的结果都是62。感谢@BoltClock
Eugen Rieck

6
利珀特先生...你在附近吗?
vc 74

6
编译器的常量表达式评估器在这里没有赢得任何奖励。显然,它在第一个表达式中截断了6.2f,在基数2中没有确切的表示形式,因此最终为6.199999。但是在第二个表达式中却没有这样做,可能是设法设法使它保持双精度。当然,这当然是标准的,浮点一致性永远不是问题。您不会解决此问题,您知道解决方法。
汉斯·帕桑

Answers:


168

首先,我假设您知道6.2f * 10由于浮点舍入而不是62(实际上是61.99999809265137的值,用a表示double),并且您的问题只是关于为什么两个看似相同的计算导致错误的值。

答案是,对于(int)(6.2f * 10),您将double取值61.99999809265137并将其截断为整数,得到61。

对于float f = 6.2f * 10,您将双精度值61.99999809265137 取整到最接近的float值62。然后将其截断float为整数,结果为62。

练习:解释以下操作序列的结果。

double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2

更新:由于在评论所指出的,表达6.2f * 10是一个正式的float,因为第二参数具有一个隐式转换到float更好比隐式转换double

实际的问题是允许(但不是必需)编译器使用比正式类型(第11.2.2节)更高精度的中间件。这就是为什么您在不同的系统上看到不同的行为的原因:在表达式中(int)(6.2f * 10),编译器可以选择6.2f * 10在转换为之前将值保持为高精度中间形式int。如果是,则结果为61。如果不是,则结果为62。

在第二个示例中,显式赋值float强制四舍五入发生在转换为整数之前。


6
我不确定这是否能真正回答问题。为什么(int)(6.2f * 10)double取值(如f指定的那样)float?我认为要点(尚无答案)在这里。
ken2k 2012年

1
我认为是由编译器执行的,因为它是浮点文字* int文字,所以编译器决定可以自由使用最佳数值类型,并且为了节省精度,它已经花了两倍(也许)。(也可以解释IL是否相同)
乔治·达基特

5
好点子。6.2f * 10实际上的类型float不是double。我认为编译器正在根据11.1.6的最后一段所允许的优化中间件。
Raymond Chen

3
它确实具有相同的值(值为61.99999809265137)。区别在于值成为整数的路径。在一种情况下,它直接变为整数,而在另一种情况下,它float首先进行转换。
Raymond Chen

38
雷蒙德在这里的答案当然是完全正确的。我注意到,C#编译器和JIT编译器都允许使用更精确的在任何时间,并因此做的不一致。实际上,他们就是这样做的。这个问题在StackOverflow上已经出现了数十次。有关最新示例,请参见stackoverflow.com/questions/8795550/…
埃里克·利珀特

11

描述

浮点数很少准确。6.2f就像6.1999998...。如果将其强制转换为int值,它将被截断,并且* 10的结果为61。

查看Jon Skeets DoubleConverter类。使用此类,您可以真正将浮点数的值可视化为字符串。Double并且float都是浮点数,十进制不是(定点数)。

样品

DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875

更多信息


5

看一下IL:

IL_0000:  ldc.i4.s    3D              // speed1 = 61
IL_0002:  stloc.0
IL_0003:  ldc.r4      00 00 78 42     // tmp = 62.0f
IL_0008:  stloc.1
IL_0009:  ldloc.1
IL_000A:  conv.i4
IL_000B:  stloc.2

编译器将编译时常量表达式缩减为它们的常量值,并且我认为在将常量转换为时,在某个时候会做出错误的近似int。在的情况下speed2,此转换不是由编译器进行的,而是由CLR进行的,并且它们似乎应用了不同的规则...


1

我的猜测是,6.2f使用float精度实表示是6.1999999,而62f很可能是类似的东西62.00000001(int)强制转换总是将十进制值截断,所以这就是为什么您会得到这种行为。

编辑:根据评论,我将int强制转换的行为改写为更精确的定义。


强制转换为int截断十进制值,它不舍入。
Jim D'Angelo 2012年

@James D'Angelo:对不起,英语不是我的主要语言。不知道确切的词,所以我将行为定义为“在处理正数时取整”,基本上描述了相同的行为。但是,是的,要指出的是,截断是确切的词。
2012年

没问题,这只是讽刺,但是如果有人开始思考float-> int涉及四舍五入会造成麻烦。= D
Jim D'Angelo 2012年

1

我编译并反汇编了此代码(在Win7 / .NET 4.0上)。我猜编译器将浮动常量表达式评估为double。

int speed1 = (int)(6.2f * 10);
   mov         dword ptr [rbp+8],3Dh       //result is precalculated (61)

float tmp = 6.2f * 10;
   movss       xmm0,dword ptr [000004E8h]  //precalculated (float format, xmm0=0x42780000 (62.0))
   movss       dword ptr [rbp+0Ch],xmm0 

int speed2 = (int)tmp;
   cvttss2si   eax,dword ptr [rbp+0Ch]     //instrunction converts float to Int32 (eax=62)
   mov         dword ptr [rbp+10h],eax 

0

Single仅保留7位数字,并且在将其转换为a时Int32,编译器将截断所有浮点数字。转换期间,一个或多个有效数字可能会丢失。

Int32 speed0 = (Int32)(6.2f * 100000000); 

给出619999980的结果,所以(Int32)(6.2f * 10)给出61。

将两个Single相乘时是不同的,在这种情况下,没有截断运算,只有近似值。

请参阅http://msdn.microsoft.com/en-us/library/system.single.aspx


-4

您是否将类型强制转换为原因int而不是解析?

int speed1 = (int)(6.2f * 10)

然后会读

int speed1 = Int.Parse((6.2f * 10).ToString()); 

差异可能与舍入有关:如果double强制转换,则可能会得到类似于61.78426的信息。

请注意以下输出

int speed1 = (int)(6.2f * 10);//61
double speed2 = (6.2f * 10);//61.9999980926514

这就是为什么您获得不同的价值!


1
Int.Parse将字符串作为参数。
ken2k 2012年

您只能解析字符串,我想您是说您为什么不使用System.Convert
vc 74
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.