简单浮点计算出错

时间:2017-02-06 11:32:34

标签: c# floating-point

我遇到了麻烦:

int q = 150;
float s = 0.7f;
float z = q*s;
int z1 = (int) (q*s);
int z2 = (int) z;

这导致

  • z1 int的值为104
  • z2 int的值为105

任何人都能解释一下吗?我不明白这些结果。

为了避免关闭,我(RenéVogt)添加了这些信息:

  • q*s会产生float的值105.0f(或者104.999999,但字符串表示最终为105)。
  • 因此zfloat
  • 105

问题现在是,为什么(int)z会产生105,但(int)(q*s)会产生104?我可以在我的机器上重现这个(i7,Win10,VS2015,.NET4.6.1)

和IL代码:

// Initialisation
// q = 150
ldc.i4 0x96  
stloc.0
// s = 0.7f
ldc.r4 0.69999999 
stloc.1

// calculating z 
ldloc.0 // load q
conv.r4 // convert to float
ldloc.1 // load s
mul     // q*s
stloc.2 // store float result to z

// calulating z1
ldloc.0 // load q
conv.r4 // convert to float
ldloc.1 // load s
mul     // q*s
conv.i4 // convert to int
stloc.3 // store int result in z1 => 104!

// calculating z2
ldloc.2 // loading float z
conv.i4 // converting to int
stloc.s // write to z2 (last local variable -> "s" as stack address)
        // => 105

所以我认为z1z2之间的唯一区别是,对于z2,中间float结果会从寄存器写入局部变量({{1}存储位置。但是这会对结果产生怎样的影响?

3 个答案:

答案 0 :(得分:6)

数字0.7无法用float完全表示,而s的值更接近0.699999988079071044921875
int的{​​{1}}值将转换为q,因为这可以直接表示为float

如果将两者相乘,则不会完全得到150

  

q = 150
  s = 0.699999988079071044921875
  q * s = 104.999998211861

现在参考CLI Spec (ECMA-335)§12.1.3中的相关部分:

  

当内部表示具有比其标称类型更大的范围和/或精度的浮点值被放入存储位置时,它将自动强制转换为存储位置的类型。这可能涉及   精度损失或创建超出范围的值(NaN,+无穷大或无穷大)。但是,如果在未经修改的情况下从存储位置重新加载该值,则该值可能会保留在内部表示中以供将来使用。编译器有责任确保保留值在后续加载时仍然有效,同时考虑到别名和其他执行线程的影响(请参阅内存模型(第12.6节))。但是,在执行显式转换(conv.r4或conv.r8)之后,不允许进行额外精度的自由,此时内部表示必须在关联类型中准确表示。

因此105会产生一个精度高于q * s可以处理的值。将其直接存储到float

int

该值永远不会强制转换为var z1 = (int)(q * s); 类型,而是直接转换为float,从而被截断为104.

在所有其他示例中,值被转换为或存储在int中,因此转换为最接近的float值,即105.

答案 1 :(得分:0)

我猜你在x64上运行这个?

因此我假设乘法发生为double。但是,double会被退回到float进行存储(因此例如为105.000000001)

mul     // q*s
stloc.2 // store float result to z (stored as float)

当直接相乘并转换为int时,乘法发生为double,可以表示为例如int。 104.999999,并截断为mul // q*s conv.i4 // convert to int (104)

temp = {"1": "hello", "2": "goodbye", "3": "hello", "4": "goodbye", "5": "hi"}

查看此答案:Strange behaviour when casting a float to int in C#

编辑:在第一个实例中,编译器没有选项,只能使用浮点运算,因为它需要将它存储回浮点数。但是对于直接转换为int的第二个选项,它可以根据链接的答案使用更高精度的算法。

答案 2 :(得分:-1)

当转换为二进制到十进制时,你不能完全表示相同的数字(rember binary只有2个charaml,其中deciaml有10个)。

十进制当量试图写在纸上1/3 = 0.3333333333333 ......(无穷大!)

由于浮点数不能代表递归数字,因此最终只能存储0.333333而非0.333333333333 ....这会导致精度下降....(简化示例)这对于低精度应用程序和性能优势非常有用(当您考虑时)数百万计算)

相反,你应该使用Decimal或Double类型,因为它们能够使用科学符号表示来存储数字,这种表示法不会轻易地降低精度。

我遇到过的最好的解释是:https://www.youtube.com/watch?v=PZRI1IfStY0