代码之家  ›  专栏  ›  技术社区  ›  jonathanpeppers

C浮动无限循环

  •  15
  • jonathanpeppers  · 技术社区  · 15 年前

    C(.NET 3.5 SP1)中的以下代码是我的计算机上的无限循环:

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

    达到16777216.0,16777216.0+1计算为16777216.0。然而在这一点上:我+1!= I.

    这有点疯狂。

    我意识到浮点数的存储方式有些不准确。我读过,大于2^24的整数不能正确地存储为浮点数。

    尽管如此,上面的代码在C中应该是有效的,即使数字不能正确表示。

    为什么不起作用?

    你可以让同样的事情发生在两倍,但这需要很长的时间。9007199254740992.0是双精度的限制。

    5 回复  |  直到 12 年前
        1
  •  21
  •   John Gietzen    15 年前

    是的,所以问题是,为了在浮动中添加一个,它必须

    16777217.0
    

    恰好如此,这是在基数的边界上,不能精确地表示为一个浮点。(下一个可用的最高值是 16777218.0 )

    所以,它四舍五入到最近的可表示浮点

    16777216.0
    

    让我这样说:

    因为你有一个 浮动 精度的数量,你必须增加一个越来越高的数字。

    编辑:

    好吧,这有点难解释,但是试试这个:

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

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

    编辑2

    根据我的计算,至少有128个二进制数字 未签名的 这是必要的。

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

    作为问题的解决方案,您可以循环使用两个128位数字。然而,这至少需要10年才能完成。

        2
  •  8
  •   ChrisW    15 年前

    例如,假设一个浮点数由最多2个有效的十进制数字加上一个指数表示:在这种情况下,您可以精确地从0到99计数。下一个是100,但因为您只能有两个有效数字存储为“1.0乘以10的2次方”。再加一个…什么?

    充其量,中间结果是101,实际存储(通过舍入误差,舍去不重要的第3位)为“1.0乘以10的2次幂”。

        3
  •  6
  •   tzenes    15 年前

    要想了解出了什么问题,你必须阅读IEEE标准 floating point

    我们来检查一下 floating point 一秒钟的数字:

    一个浮点数被分成两部分(确定3,但忽略符号位一秒钟)。

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

    smmmmmmmmeeeeeee
    

    注意:这并不是对比特数的精确估计,但它能让你对正在发生的事情有一个大致的了解。

    为了计算出您拥有的数量,我们进行以下计算:

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

    那么float.maxvalue会是什么呢?你会得到最大的尾数和最大的指数。让我们假设这看起来像:

    01111111111111111
    

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

    所以,当你 9.9999*2^99 + 1 ?好吧,你没有足够的有效数字来加1。结果,它被四舍五入为相同的数字。在单浮点精度的情况下, +1 开始四舍五入恰好是 16777216.0

        4
  •  2
  •   Chris Dunaway    15 年前

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

    下面是乔恩·斯基特写的一节课,说明了这一点:

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

        5
  •  -4
  •   aaaa bbbb    15 年前

    当我接近float.maxvalue时,迭代使我刚好低于这个值。下一个迭代添加到i,但它不能容纳大于float.maxvalue的数字。因此,它保存的值要小得多,并再次开始循环。