奇怪的行为时,将一个浮点数转换为int在C#

我有以下简单的代码:

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,而是一个函数调用*常量。 我有以下字节码:

对于速度1:

 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 

速度2:

 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,结果相同

首先,我假定你知道6.2f * 10由于浮点舍入而不完全是62(实际上它是以double表示的值为61.99999809265137),而且你的问题只是为什么两个看起来相同的计算导致了错误的价值。

答案是,在(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 

更新:如注释中所述,expression式6.2f * 10在forms上是一个float因为第二个参数隐式转换为float ,这比隐式转换为double 要好 。

实际的问题是,编译器被允许(但不是必需的)使用比正式types更高精度的中间体。 这就是为什么你在不同的系统上看到不同的行为:在expression式(int)(6.2f * 10) ,编译器可以在转换为int之前将值6.2f * 10保留在高精度的中间表单中。 如果是,那么结果是61.如果不是,那么结果是62。

在第二个例子中,对float的显式赋值迫使舍入在转换为整数之前发生。

描述

浮动数字很less精确。 6.2f类似6.1999998... 如果你把它转换成int,它会截断它,这个* 10的结果是61。

查看Jon Skeets DoubleConverter类。 有了这个类,你可以真正将浮点数的值看成string。 Double floatfloat都是浮点数 ,十进制不是(它是一个定点数)。

样品

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

更多信息

  • Jon Skeet的DoubleConverter类
  • Assert.AreEqual()与System.Double变得非常混乱
  • 计算机科学家应该知道什么是浮点运算

看看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 

编译器将编译时常量expression式减less为它们的常量值,并且我认为在将常量转换为int时,在某个点上会产生错误的近似值。 在速度2的情况下,这种转换不是由编译器,而是由CLR,而他们似乎适用不同的规则…

我的猜测是6.2f浮点精度的实际表示是6.199999962f可能类似于62.00000001(int)铸造总是截断小数值,所以这就是为什么你得到的行为。

编辑 :根据意见,我已经将int casting的行为更改为更精确的定义。

Single mantains只有7个数字,当将其转换为Int32 ,编译器将截断所有浮点数字。 在转换期间,一个或多个有效数字可能会丢失。

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

给出了619999980的结果(Int32)(6.2f * 10)给出了61。

当两个单倍相乘时,这是不同的,在这种情况下,不存在截断操作,而只是近似值。

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

我编译和反汇编了这段代码(在Win7 / .NET 4.0上)。 我猜编译器将浮点常量expression式计算为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 

有没有一个原因,你types转换为int而不是parsing?

 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 

这就是为什么你得到不同的价值观!