这是一个错误吗?浮动操作被视为整数

时间:2012-03-14 22:51:22

标签: c# asp.net

此操作返回0:

string value = “0.01”;
float convertedValue = float.Parse(value);
return (int)(convertedValue * 100.0f);

但是此操作返回1:

string value = “0.01”;
float convertedValue = float.Parse(value) * 100.0f;
return (int)(convertedValue);

因为convertedValue是一个浮点数,它在括号内* 100f它不应该被视为浮点运算吗?

4 个答案:

答案 0 :(得分:19)

两者之间的区别在于编译器优化浮点运算的方式。让我解释一下。

string value = "0.01";
float convertedValue = float.Parse(value);
return (int)(convertedValue * 100.0f);

在此示例中,该值被解析为80位浮点数,以便在计算机的内部浮点地牢中使用。然后将其转换为32位浮点数以存储在convertedValue变量中。这导致该值四舍五入到似乎小于0.01的数字。然后将其转换回80位浮点数并乘以100,将舍入误差增加100倍。然后它被转换为32位int。这会导致浮动被截断,并且因为它实际上略小于1,所以int转换返回0.

string value = "0.01";
float convertedValue = float.Parse(value) * 100.0f;
return (int)(convertedValue);

在此示例中,该值再次被解析为80位浮点数。然后在它转换为32位浮点数之前再乘以100。这意味着舍入误差非常小,以至于当它转换为32位浮点数以便存储在convertedValue中时,它会精确到1.然后当它转换为int时,你得到1。

主要思想是计算机使用高精度浮点数进行计算,然后在将值存储在变量中时对值进行舍入。使用浮点数的分配越多,舍入错误就越多。

答案 1 :(得分:14)

请阅读浮点介绍。这是典型的浮点问题。二进制浮点不能完全代表0.01

0.01 * 100约为1。

如果恰好四舍五入到0.999...,则会得到0,如果它被舍入到1.000...,则得到1.你得到的那个是未定义的。

每次遇到类似的表达式(甚至不同的上下文中的相同表达式)时,jit编译器都不需要以相同的方式进行舍入。特别是它可以随时使用更高的精度,但如果它认为这是一个好主意,可以降级到32位浮点数。


一个有趣的观点是对float的显式转换(即使您已经有类型float的表达式)。这会强制JITer将精度降低到该点的32位浮点数。但确切的舍入仍未定义。

由于舍入未定义,因此可以在.net版本,调试/发布版本,调试器的存在(以及可能的月相:P)之间变化。

  

浮点数(静态,数组元素和类的字段)的存储位置具有固定大小。该   支持的存储大小是float32和float64。其他地方(在评估堆栈上,作为参数,如   返回类型,作为局部变量)浮点数使用内部浮点表示   类型。

     

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


使用Decimal可以解决您的具体问题,但3*(1/3f)的类似问题将无法解决,因为Decimal不能完全代表三分之一。< / p>

答案 2 :(得分:2)

在这一行:

(int)(convertedValue * 100.0f)

中间值实际上具有更高的精度,而不仅仅是浮点数。要获得与第二个相同的结果,您必须这样做:

(int)((float)(convertedValue * 100.0f))

在IL级别上,差异如下:

    mul 
    conv.i4 

与您的第二个版本对比:

    mul 
    stloc.3 
    ldloc.3 
    conv.i4 

请注意,第二个存储/恢复float32变量中的值,这会强制它具有float精度。 (请注意,根据CodeInChaos的评论,规范不保证这一点。)

(为了完整性,显式转换看起来像:)

    mul 
    conv.r4 
    conv.i4 

答案 3 :(得分:-1)

我知道这个问题并且总是使用它。 正如我们的朋友CodeInChaose所回答的那样,浮点数不会作为其存在于内存中。

但是我想补充说你有不同结果的原因,不是因为JIT可以自由使用他想要的精度。

原因在于你的第一个代码你转换了字符串并将其保存在内存中,所以在这种情况下它不会被保存为0.1,有些将如何保存0.0999966或类似这样的数字。

在第二个代码中进行转换,然后将其保存在内存中,然后在内存中分配值之前,您进行了乘法运算,这样您就可以得到正确的结果而不会冒浮点数的JIT精度。< / p>