Пример в Gotw 67 - PullRequest
       22

Пример в Gotw 67

6 голосов
/ 09 августа 2011

Есть пример в http://www.gotw.ca/gotw/067.htm

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

Когда вы меняете double на float, это бесконечный цикл в VS2008.Согласно объяснению Готва:

Что если float не может точно представлять все целочисленные значения от 0 до 1e8?Затем измененная программа начнет обратный отсчет, но в конечном итоге достигнет значения N, которое не может быть представлено и для которого N-1 == 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 ]

8 голосов
/ 09 августа 2011

Что касается аргументов, давайте предположим, что у нас есть процессор, который представляет число с плавающей запятой с 7 значащими десятичными цифрами, и мантисса, скажем, с 2 десятичными цифрами.Таким образом, теперь число 1e8 будет сохранено как

1.000 000 e 08

(где «.» И «e» не нужно хранить на самом деле.)

Итак, теперь вы хотите вычислить «1e8 -1" .1 представляется как

1.000 000 e 00

Теперь, чтобы выполнить вычитание, мы сначала делаем вычитание с бесконечной точностью, а затем нормализуем так, чтобы первая цифра перед "."находится между 1 и 9, и, наконец, округляется до ближайшего представимого значения (с, скажем, безубыточным).Результат бесконечной точности "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, которые немного отличаются, но принцип примерно такой же.

3 голосов
/ 09 августа 2011

Операция x-- (в данном случае) эквивалентна x = x - 1. Это означает, что берется исходное значение x, 1 вычитается (с бесконечной точностью, как указано в 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.0e8f и 1.0e8f + 4 и некоторые другие числа имеют одинаковое представление. Поскольку вы уже знакомы с деталями форматов IEEE 754-1985 с плавающей запятой, вы также знаете, что оставшиеся цифры должны быть округлены.

1 голос
/ 09 августа 2011

Что касается "достижения" значения, которое не может быть представлено, я думаю, что Херб включал возможность довольно эзотерических представлений с плавающей точкой.

С любыми обычными представлениями с плавающей точкой вы либо начнете с такого значения(т.е. застрял на первом значении), или вы окажетесь где-то в непрерывном диапазоне целых чисел с центром вокруг нуля, который может быть точно представлен, так что обратный отсчет завершится успешно.

Для IEEE 754 32-битное представление, обычноfloat в C ++ имеет 23-битную мантиссу, в то время как 64-битное представление, обычно double в C ++, имеет 52-битную мантиссу.Это означает, что с double вы можете, по крайней мере, точно представить целые числа в диапазоне - (2 ^ 52-1) ... 2 ^ 52-1.Я не совсем уверен, может ли диапазон быть расширен с другим фактором 2. У меня немного кружится голова, думая об этом.: -)

Приветствия и hth.,

1 голос
/ 09 августа 2011

Что является результатом n - 1 , если n - 1 и n имеют оба идентичных представления из-за приблизительной природы чисел с плавающей запятой?

...