C#(。Net 3.5 SP1)中的以下代码是我机器上的无限循环:
for (float i = 0; i < float.MaxValue; i++) ;
它达到了数字16777216.0并且16777216.0 + 1被评估为16777216.0。但在这一点上:i + 1!= i。
这是一种疯狂。
我意识到浮点数的存储方式存在一些不准确之处。而且我已经读过整数大于2 ^ 24而不能正确存储为浮点数。
上面的代码仍然应该在C#中有效,即使数字无法正确表示。
为什么不起作用?
你可以得到同样的双重发生,但需要很长时间。 9007199254740992.0是double的限制。
答案 0 :(得分:21)
是的,所以问题在于,为了向浮点数添加一个,它必须变为
16777217.0
恰好这是在基数的边界处,并且不能完全表示为浮点数。 (可用的下一个最高值是16777218.0
)
因此,它舍入到最近的可表示的浮点数
16777216.0
让我这样说吧:
由于你有浮动精度,你必须增加一个更高和更高的数字。
修改强>
好的,这有点难以解释,但试试这个:
float f = float.MaxValue;
f -= 1.0f;
Debug.Assert(f == float.MaxValue);
这将运行得很好,因为在该值,为了表示1.0f的差异,您将需要超过128位的精度。浮点只有32位。
<强> EDIT2 强>
根据我的计算,至少需要128个二进制数字 unsigned 。
log(3.40282347E+38) * log(10) / log(2) = 128
作为问题的解决方案,您可以遍历两个128位数字。但是,这至少需要十年才能完成。
答案 1 :(得分:8)
想象一下,例如浮点数由最多2个有效十进制数字加上指数表示:在这种情况下,您可以精确计数从0到99。接下来将是100,但因为你只能有2位有效数字存储为“1.0倍10到2的幂”。添加一个就是......什么?
充其量,它将是101作为中间结果,实际上将存储(通过舍入误差,丢弃无效的第3位数)为“1.0倍10到2的幂”。
答案 2 :(得分:6)
要了解出现了什么问题,您必须阅读floating point上的IEEE标准
让我们检查一下floating point数字的结构:
浮点数分为两部分(ok 3,但忽略符号位一秒)。
你有一个指数和一个尾数。像这样:
smmmmmmmmeeeeeee
注意:这与位数不符,但它可以让您大致了解正在发生的事情。
要确定您的号码,我们会进行以下计算:
mmmmmm * 2^(eeeeee) * (-1)^s
那么float.MaxValue会是什么?那么你将拥有最大可能的尾数和最大可能的指数。让我们假装看起来像这样:
01111111111111111
实际上我们定义了NAN和+ -INF以及其他一些约定,但是暂时忽略它们,因为它们与你的问题无关。
那么,当你9.9999*2^99 + 1
时会发生什么?好吧,你没有足够的重要数字来添加1.因此它会向下舍入到相同的数字。在单浮点精度的情况下,+1
开始向下舍入的点恰好是16777216.0
答案 3 :(得分:2)
它与溢出无关,或接近最大值。 16777216.0的浮点值具有16777216的二进制表示。然后将其递增1,因此它应该是16777217.0,除了16777217.0的二进制表示是16777216 !!!所以它实际上并没有增加,或者至少增量没有达到预期的效果。
这是由Jon Skeet撰写的一个课程,用于说明这一点:
尝试使用此代码:
double d1 = 16777217.0;
Console.WriteLine(DoubleConverter.ToExactString(d1));
float f1 = 16777216.0f;
Console.WriteLine(DoubleConverter.ToExactString(f1));
float f2 = 16777217.0f;
Console.WriteLine(DoubleConverter.ToExactString(f2));
注意16777216.0的内部表示是如何相同的16777217.0 !!
答案 4 :(得分:-4)
当我接近float.MaxValue时的迭代我刚好低于这个值。下一次迭代添加到i,但它不能保存比float.MaxValue更大的数字。因此它保持一个更小的值,并再次开始循环。