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

时间:2012-01-18 14:04:01

标签: c# casting floating-point int expression

我有以下简单的代码:

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

speed1speed2应该具有相同的值,但事实上,我有:

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

7 个答案:

答案 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,因为第二个参数隐式转换为floatbetter比隐式转换为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课程。使用此类,您可以将浮点数的值可视化为字符串。 Doublefloat都是浮点数,十进制不是(它是固定点数)。

示例

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

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