C#浮动无限循环

C#(。Net 3.5 SP1)中的以下代码是我机器上的无限循环:

for (float i = 0; i < float.MaxValue; i++) ; 

它达到了16777216.0和16777216.0 + 1的评估值为16777216.0。 但在这一点上:我+ 1!=我。

这是一些疯狂。

我意识到浮点数的存储方式存在一些不准确之处。 而且我已经读过整数大于2 ^ 24但不能正确存储为浮点数。

上面的代码仍然应该在C#中有效,即使数字无法正确表示。

为什么不起作用?

你可以在double中获得同样的效果,但这需要很长时间。 9007199254740992.0是double的限制。

是的,所以问题在于,为了向浮点数添加一个,它必须成为

 16777217.0 

恰好这是在基数的边界处,并且不能完全表示为浮点数。 (下一个最高值是16777218.0

因此,它舍入到最近的可表示浮点数

 16777216.0 

让我这样说吧:

由于您具有浮动的精度,因此必须增加更高和更高的数字。

编辑:

好的,这有点难以解释,但试试这个:

 float f = float.MaxValue; f -= 1.0f; Debug.Assert(f == float.MaxValue); 

这将运行得很好,因为在该值,为了表示1.0f的差异,您将需要超过128位的精度。 浮点只有32位。

EDIT2

根据我的计算,至少需要128个无符号二进制数字。

 log(3.40282347E+38) * log(10) / log(2) = 128 

作为问题的解决方案,您可以循环访问两个128位数字。 但是,这至少需要十年时间才能完成。

想象一下,例如浮点数由最多2个有效十进制数字加上指数表示:在这种情况下,您可以精确计数从0到99。 接下来将是100,但因为你只能有2位有效数字存储为“1.0倍10到2的幂”。 添加一个就是……什么?

充其量,它将是101作为中间结果,其实际上将被存储(通过舍入误差,其丢弃不重要的第三数字)再次为“1.0倍10到2的幂”。

要了解出了什么问题,您将不得不阅读关于浮点的IEEE标准

让我们检查一下浮点数的结构:

浮点数分为两部分(ok 3,但忽略符号位一秒)。

你有一个指数和一个尾数。 像这样:

 smmmmmmmmeeeeeee 

注意:这与位数不符,但它可以让您大致了解发生了什么。

要计算出你的数字,我们进行以下计算:

 mmmmmm * 2^(eeeeee) * (-1)^s 

那么float.MaxValue会是什么? 那么你将拥有最大可能的尾数和最大可能的指数。 让我们假装看起来像这样:

 01111111111111111 

实际上我们定义了NAN和+ -INF以及其他几个约定,但是暂时忽略它们因为它们与你的问题无关。

那么,当你有9.9999*2^99 + 1时会发生什么? 好吧,你没有足够的重要数据来添加1.因此它会向下舍入到相同的数字。 在单浮点精度的情况下, +1开始向下舍入的点恰好是16777216.0

它与溢出无关,或接近最大值。 16777216.0的浮点值具有16777216的二进制表示。然后将其递增1,因此它应该是16777217.0,除了16777217.0的二进制表示是16777216 !!! 所以它实际上并没有增加,或者至少增量没有达到预期的效果。

这是由Jon Skeet编写的一个类,它说明了这一点:

DoubleConverter.cs

试试这段代码吧:

 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 !!

当我接近float.MaxValue时的迭代我刚好低于这个值。 下一次迭代会添加到i,但它不能包含大于float.MaxValue的数字。 因此它保持一个更小的值,并再次开始循环。