【问题标题】:C# float infinite loopC# 浮动无限循环
【发布时间】:2010-02-08 20:19:00
【问题描述】:

以下 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# 中也应该是有效的。

为什么它不起作用?

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

【问题讨论】:

标签: c# .net loops floating-point double


【解决方案1】:

是的,所以问题是为了给浮点数加一个,它必须变成

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 位数字。但是,这至少需要十年才能完成。

【讨论】:

  • C# 应该允许它递增,但对吗?任何 float + 1 都应该大于 float 本身,对吧? i + 1 > i 无论如何?如果有溢出,我会说否则,但数字远不及 float.MaxValue.
  • @Jonathan.Peppers:我不确定您对浮点数系统的这种误解是从哪里来的。我建议你在维基百科上阅读它们,然后重新评估你的代码。 en.wikipedia.org/wiki/Floating_point
  • 好的,这样想:浮点数有固定的位数。在float.MaxValue 附近,该值不精确。事实上,大部分无关紧要的数字都被截断了。尝试从 float.MaxValue 中减一。
  • @Jonathan.Peppers:此时你需要证明你已经阅读了维基百科的文章,否则很明显你只是在拖钓。
  • 或者至少阅读约翰的评论。它运行正常,只是没有足够的精度来表示 10^0 增量。
【解决方案2】:

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

充其量,它是 101 作为中间结果,实际上将再次存储为“1.0 乘以 10 的 2 次方”(通过舍入错误丢弃不重要的第 3 位)。

【讨论】:

    【解决方案3】:

    要了解发生了什么问题,您必须阅读 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

    【讨论】:

      【解决方案4】:

      它与溢出无关,或接近最大值。 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 相同!!

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-26
          • 2013-05-30
          • 2011-07-26
          • 2016-08-27
          • 2015-04-29
          • 1970-01-01
          相关资源
          最近更新 更多