Значительные цифры увеличиваются - PullRequest
2 голосов
/ 25 февраля 2012

Давайте,

float dt;

Я читаю dt из текстового файла как

inputFile >> dt;

Тогда у меня есть for цикл как,

for (float time=dt; time<=maxTime; time+=dt)
{
    // some stuff
}

Когда dt=0.05 и я вывожу std::cout << time << std::endl; Я получил,

0.05
0.10
...
7.00001
7.05001
...

Итак, почему число цифр увеличивается через некоторое время?

Ответы [ 3 ]

6 голосов
/ 25 февраля 2012

Поскольку не каждое число может быть представлено значениями с плавающей запятой IEEE754.В какой-то момент вы получите число, которое не совсем представимо, и компьютер должен будет выбрать ближайший.

Если вы введете 0,05 в Harald Schmidt's excellent online converter и сделаете ссылку на Запись в Википедии по IEEE754-1985 , в итоге вы получите следующие биты (мое объяснение этого следует):

   s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
   0 01111010 10011001100110011001101
     |||||||| |||||||||||||||||||||||
128 -+||||||| ||||||||||||||||||||||+- 1 / 8388608
 64 --+|||||| |||||||||||||||||||||+-- 1 / 4194304
 32 ---+||||| ||||||||||||||||||||+--- 1 / 2097152
 16 ----+|||| |||||||||||||||||||+---- 1 / 1048576
  8 -----+||| ||||||||||||||||||+----- 1 /  524288
  4 ------+|| |||||||||||||||||+------ 1 /  262144
  2 -------+| ||||||||||||||||+------- 1 /  131072
  1 --------+ |||||||||||||||+-------- 1 /   65536
              ||||||||||||||+--------- 1 /   32768
              |||||||||||||+---------- 1 /   16384
              ||||||||||||+----------- 1 /    8192
              |||||||||||+------------ 1 /    4096
              ||||||||||+------------- 1 /    2048
              |||||||||+-------------- 1 /    1024
              ||||||||+--------------- 1 /     512
              |||||||+---------------- 1 /     256
              ||||||+----------------- 1 /     128
              |||||+------------------ 1 /      64
              ||||+------------------- 1 /      32
              |||+-------------------- 1 /      16
              ||+--------------------- 1 /       8
              |+---------------------- 1 /       4
              +----------------------- 1 /       2

Знак, будучи 0, является положительным.Показатель степени указывается однобитным отображением чисел слева: 64+32+16+8+2 = 122 - 127 bias = -5, поэтому множитель равен 2 -5 или 1/32.Смещение 127 позволяет отображать очень маленькие числа (например, близкие к нулю, а не отрицательные числа с большой величиной).

Мантисса немного сложнее.Для каждого бита вы накапливаете числа в правой части (после добавления неявного 1).Следовательно, вы можете вычислить число как сумму {1, 1/2, 1/16, 1/32, 1/256, 1/512, 1/4096, 1/8192, 1/65536, 1/131072, 1/1048576, 1/2097152, 1/8388608}.

Когда вы сложите все это, вы получите 1.60000002384185791015625.

Когда вы умножите на намножитель 1/32 (рассчитанный ранее из битов экспоненты), вы получите 0.0500000001, так что вы можете видеть, что 0.05 это уже не представлен точно.Этот битовый шаблон для мантиссы фактически такой же, как 0.1, но при этом показатель степени равен -4, а не -5, и поэтому 0.1 + 0.1 + 0.1 редко равен 0.3 (это, кажется, любимое интервьювопрос).

Когда вы начнете добавлять их, эта небольшая ошибка будет накапливаться, поскольку вы не только увидите ошибку в самом 0.05, но и ошибки могут появляться на каждом этапе накопления - невсе числа 0.1, 0.15, 0.2 и т. д. могут быть представлены точно так же.

В конце концов, ошибки станут достаточно большими, и они начнут появляться в числе, если выиспользуйте точность по умолчанию.Вы можете отложить это на некоторое время, выбрав собственную точность, например:

#include <iostream>
#include <iomanip>
:
std::cout << std::setprecison (2) << time << '\n';

Это не изменит значение переменной , , но даст вам больше пространства для дыханиядо того, как ошибки станут видны.

Кроме того, некоторые люди рекомендуют избегать std::endl, так как это вызывает сброс буферов.Если ваша реализация работает сама по себе, это произойдет с терминальными устройствами, когда вы все равно отправите новую строку.И если вы перенаправили стандартный вывод на нетерминал, вы, вероятно, не хотите очищать каждую строку.На самом деле не имеет отношения к вашему вопросу, и, вероятно, это не будет иметь большого значения в подавляющем большинстве случаев, просто вопрос, о котором я подумала.

3 голосов
/ 25 февраля 2012

IEEE-числа используют двоичную систему счисления и поэтому не могут точно хранить десятичные числа.Когда вы добавляете несколько из них вместе (иногда достаточно двух), ошибки представления могут накапливаться и становиться видимыми.

0 голосов
/ 25 февраля 2012

Некоторые числа не могут быть точно представлены с использованием чисел с плавающей запятой ИЛИ чисел с основанием 2.Если я правильно помню, одно из таких чисел является десятичным 0,05 (в базе 2 получается бесконечно повторение дробного числа).Другая проблема заключается в том, что если вы печатаете число с плавающей точкой в ​​файл (как число 10), а затем читаете его обратно, вы также можете получить другое число - потому что база отличается и это может вызвать проблемы при преобразовании дробного числа 2 в дробное число числа 10.1004 * Если вы хотите повысить точность, попробуйте поискать библиотеку bignum.Это будет работать намного медленнее, чем с плавающей запятой.Другой способ справиться с проблемами точности - попытаться сохранить числа как «общую дробь» с нумератором / знаменателем (т.е. 1/10 вместо 0,1, 1/3 вместо 0,333 и т. Д. - возможно, есть библиотека даже для этого, ноЯ не слышал об этом), но это не сработает с иррациональными числами, такими как pi или e .

...