此操作返回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它不应该被视为浮点运算吗?
答案 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>