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 SkeetsDoubleConverter
class。使用这个类,您可以将浮点数的值可视化为字符串Double
和float
都是浮点数,十进制则不是(它是一个固定点数)
样品
更多信息
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
截断十进制值,