奇怪的行为时,将一个浮点数转换为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
float
和float
都是浮点数 ,十进制不是(它是一个定点数)。
样品
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.1999999
而62f
可能类似于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
这就是为什么你得到不同的价值观!