6

http://www.gotw.ca/gotw/067.htm中有一个例子

int main()
{
  double x = 1e8;
  //float x = 1e8;
  while( x > 0 )
  {
    --x;
  }
}

当您将双精度更改为浮点时,它在 VS2008 中是一个无限循环。根据Gotw的解释:

如果 float 不能准确表示从 0 到 1e8 的所有整数值怎么办?然后修改后的程序将开始倒计时,但最终会达到一个无法表示且 N-1 == N 的值 N(由于浮点精度不足)......然后循环将卡住直到运行程序的机器没电为止。

据我了解,IEEE754 浮点数是单精度(32 位),浮点数的范围应该是 +/- 3.4e +/- 38,它应该有 7 位有效。

但我仍然不明白这是怎么发生的:“最终达到一个无法表示的值 N 并且 N-1 == N (由于浮点精度不足)。” 有人可以尝试解释这一点吗?

一些额外的信息:当我使用 double x = 1e8 时,它在大约 1 秒内完成,当我将其更改为 float x = 1e8 时,它运行的时间要长得多(5 分钟后仍然运行),如果我将其更改为float x = 1e7;,它在大约 1 秒内完成。

我的测试环境是VS2008。

顺便说一句,我不是在问基本的 IEEE 754 格式解释,因为我已经理解了。

谢谢

4

4 回答 4

8

好吧,为了争论,假设我们有一个处理器,它表示一个具有 7 个有效十进制数字的浮点数和一个具有 2 个十进制数字的尾数。所以现在数字 1e8 将被存储为

1.000 000 e 08

(其中“.”和“e”不需要实际存储。)

所以现在你想计算“1e8 - 1”。1 表示为

1.000 000 e 00

现在,为了进行减法,我们首先以无限精度进行减法,然后进行归一化,以便“。”之前的第一个数字。介于 1 和 9 之间,最后四舍五入到最接近的可表示值(例如,在偶数上使用 break)。"1e8 - 1" 的无限精度结果是

0.99 999 999 e 08

或归一化

9.9 999 999 e 07

可以看出,无限精度结果需要的有效位数比我们的架构实际提供的多一位;因此我们需要将无限精确的结果四舍五入(并重新归一化)到 7 个有效数字,从而得到

1.000 000 e 08

因此,您最终得到“1e8 - 1 == 1e8”,并且您的循环永远不会终止。

现在,实际上您使用的是 IEEE 754 二进制浮点数,这有点不同,但原理大致相同。

于 2011-08-09T13:58:40.810 回答
3

该操作x--(在这种情况下)等效于x = x - 1. 这意味着取原始值x1减去(使用无限精度,按照 IEEE 754-1985 的要求),然后将结果四舍五入到float值空间的下一个值。

1.0e8f + i数字的四舍五入结果i in [-10;10]如下:

 -10: 9.9999992E7     (binary +|10011001|01111101011110000011111)
  -9: 9.9999992E7     (binary +|10011001|01111101011110000011111)
  -8: 9.9999992E7     (binary +|10011001|01111101011110000011111)
  -7: 9.9999992E7     (binary +|10011001|01111101011110000011111)
  -6: 9.9999992E7     (binary +|10011001|01111101011110000011111)
  -5: 9.9999992E7     (binary +|10011001|01111101011110000011111)
  -4: 1.0E8           (binary +|10011001|01111101011110000100000)
  -3: 1.0E8           (binary +|10011001|01111101011110000100000)
  -2: 1.0E8           (binary +|10011001|01111101011110000100000)
  -1: 1.0E8           (binary +|10011001|01111101011110000100000)
   0: 1.0E8           (binary +|10011001|01111101011110000100000)
   1: 1.0E8           (binary +|10011001|01111101011110000100000)
   2: 1.0E8           (binary +|10011001|01111101011110000100000)
   3: 1.0E8           (binary +|10011001|01111101011110000100000)
   4: 1.0E8           (binary +|10011001|01111101011110000100000)
   5: 1.00000008E8    (binary +|10011001|01111101011110000100001)
   6: 1.00000008E8    (binary +|10011001|01111101011110000100001)
   7: 1.00000008E8    (binary +|10011001|01111101011110000100001)
   8: 1.00000008E8    (binary +|10011001|01111101011110000100001)
   9: 1.00000008E8    (binary +|10011001|01111101011110000100001)
  10: 1.00000008E8    (binary +|10011001|01111101011110000100001)

所以你可以看到1.0e8f1.0e8f + 4以及其他一些数字具有相同的表示。由于您已经了解 IEEE 754-1985 浮点格式的详细信息,因此您也知道剩余的数字必须已四舍五入。

于 2011-08-09T14:37:19.400 回答
1

由于浮点数的近似性质,如果n - 1n具有相同的表示,则n - 1的结果是什么?

于 2011-08-09T12:16:19.910 回答
1

关于“达到”一个无法表示的值,我认为 Herb 包括了非常深奥的浮点表示的可能性。

对于任何普通的浮点表示,您要么从这样的值开始(即停留在第一个值上),要么处于可以精确表示的以零为中心的连续整数范围内的某个位置,以便倒计时成功。

对于 IEEE 754,通常在 C++ 中的 32 位表示float具有 23 位尾数,而通常double在 C++ 中的 64 位表示具有 52 位尾数。这意味着double您至少可以准确地表示 -(2^52-1) ... 2^52-1 范围内的整数。我不太确定是否可以将范围扩大到另一个 2 倍。想到它我有点头晕。:-)

干杯&hth.,

于 2011-08-09T12:22:46.393 回答