我有以下简单的代码:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
和speed2
应该具有相同的值,但事实上,我有:
speed1 = 61
speed2 = 62
我知道我应该使用Math.Round而不是cast,但我想理解为什么值不同。
我查看了生成的字节码,但除了存储和加载外,操作码是相同的。
我也在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,结果相同。
答案 0 :(得分:166)
首先,我假设您知道6.2f * 10
由于浮点舍入(实际上是表示为double
时的值61.99999809265137)并不完全是62,并且您的问题仅是关于为什么两个看似相同的计算会导致错误的值。
答案是,在(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
,better比隐式转换为higher precision than the formal type (section 11.2.2)转换为double
。
实际问题是允许编译器(但不是必需)使用{{3}}的中间件。这就是您在不同系统上看到不同行为的原因:在表达式(int)(6.2f * 10)
中,编译器可以选择在转换为6.2f * 10
之前将值int
保持为高精度中间形式。如果是,则结果为61.如果没有,则结果为62。
在第二个示例中,对float
的显式赋值强制在转换为整数之前进行舍入。
答案 1 :(得分:11)
浮动数字很少。 6.2f
类似6.1999998...
。
如果将其强制转换为int,则会截断它,此* 10将导致61。
查看Jon Skeets DoubleConverter
课程。使用此类,您可以将浮点数的值可视化为字符串。 Double
和float
都是浮点数,十进制不是(它是固定点数)。
DoubleConverter.ToExactString((6.2f * 10))
// output 61.9999980926513671875
答案 2 :(得分:4)
看看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进行的,它们似乎应用了不同的规则......
答案 3 :(得分:1)
我的猜测是6.2f
浮点精度的真实表示是6.1999999
,而62f
可能与62.00000001
类似。 (int)
转换始终截断小数值,这就是您获得该行为的原因。
编辑:根据评论,我已将int
投射的行为改为更精确的定义。
答案 4 :(得分: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
答案 5 :(得分:0)
Single
仅保留7位数,当将其转换为Int32
时,编译器会截断所有浮点数。在转换过程中,可能会丢失一个或多个有效数字。
Int32 speed0 = (Int32)(6.2f * 100000000);
给出结果619999980,因此(Int32)(6.2f * 10)给出61。
当两个Single相乘时,它会有所不同,在这种情况下,没有截断操作,只有近似值。
请参阅http://msdn.microsoft.com/en-us/library/system.single.aspx
答案 6 :(得分:-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
这就是为什么你得到不同的价值观!