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

C#Float表达式:将结果Float转换为int时的奇怪行为,c#,casting,floating-point,int,expression,C#,Casting,Floating Point,Int,Expression,我有以下简单的代码: int speed1 = (int)(6.2f * 10); float tmp = 6.2f * 10; int speed2 = (int)tmp; speed1和speed2应该有相同的值,但事实上,我有: speed1 = 61 speed2 = 62 我知道我可能应该使用Math.Round而不是casting,但我想了解为什么值不同 我查看了生成的字节码,但除了存储和加载之外,操作码是相同的 我还在java中尝试了相同的代码,并且正确地获得了62和62 有人

我有以下简单的代码:

int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
speed2
应该有相同的值,但事实上,我有:

speed1 = 61
speed2 = 62
我知道我可能应该使用Math.Round而不是casting,但我想了解为什么值不同

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

我还在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
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
对于
speed2

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
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
真实表示是
6.1999999
,而
62f
可能类似于
62.00000001
<代码>(int)强制转换总是截断十进制值,这就是为什么会出现这种行为


编辑:根据评论,我已将
int
强制转换的行为重新表述为更精确的定义。

是否有理由将强制转换为
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

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

首先,我假设您知道,由于浮点舍入,
6.2f*10
并不完全是62(实际上是61.9999809265137的值,当表示为
双精度
),您的问题只是关于为什么两个看似相同的计算会导致错误的值

答案是,在
(int)(6.2f*10)
的情况下,取
的double
值61.9999809265137并将其截断为整数,得到61

如果
浮点f=6.2f*10
,则取双精度值61.9999809265137,并四舍五入到最接近的
浮点
,即62。然后将该
浮点值
截断为整数,结果为62

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

double d = 6.2f * 10;
int tmp2 = (int)d;
// evaluate tmp2
更新:如注释中所述,表达式
6.2f*10
形式上是一个
float
,因为第二个参数隐式转换为
float
,这比隐式转换为
double

实际的问题是编译器被允许(但不是必需的)使用一个中间版本。这就是为什么在不同的系统上会看到不同的行为:在表达式
(int)(6.2f*10)
中,编译器可以选择在转换为
int
之前将值
6.2f*10
保留为高精度中间形式。如果是,那么结果是61。如果没有,那么结果是62

在第二个示例中,对
float
的显式赋值强制在转换为整数之前进行舍入。

Description 浮点数很少精确
6.2f
类似于
6.1999998…
。 如果将其转换为int,它将截断它,这个*10将导致61

查看Jon Skeets
DoubleConverter
class。使用这个类,您可以将浮点数的值可视化为字符串
Double
float
都是浮点数,十进制则不是(它是一个固定点数)

样品 更多信息
看看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进行的,而且它们似乎应用了不同的规则…

单个
仅包含7位数字,当将其转换为
Int32
时,编译器会截断所有浮点数字。在转换过程中,可能会丢失一个或多个有效数字

Int32 speed0 = (Int32)(6.2f * 100000000); 
给出了6199980的结果,因此(Int32)(6.2f*10)给出了61

当两个单数相乘时就不同了,在这种情况下,没有截断运算,只有近似


请参阅我编译并反汇编了这段代码(在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 

我猜其中一个操作是单精度完成的,而另一个是双精度完成的。其中一个返回的值略小于62,因此在截断为整数时会产生61。这是典型的浮点精度问题。在.Net/WinXP、.Net/Win7、Mono/Ubuntu和Mono/OSX上尝试此操作会在两个Windows版本中给出结果,但在两个Mono版本中,speed1和speed2的结果都是62。谢谢@BoltClock Lippert先生。。。你知道吗?编译器的常量表达式计算器在这里没有获奖。很明显,它在第一个表达式中截断了6.2f,它在基2中没有精确的表示形式,因此最终为6.199999。但在第二个表达式中没有这样做,可能是通过设法使其保持双精度。这对于过程来说是另一个问题,浮点一致性永远不是问题。这不会得到解决,你知道解决方法。
Int.Parse
将字符串作为参数。你只能解析字符串,我想你的意思是为什么不使用System.ConvertCasting将
Int
截断十进制值,