C++
Арифметика с плавающей точкой
Поиск…
Числа с плавающей запятой - странные
Первая ошибка, которую делает почти каждый программист, предполагает, что этот код будет работать по назначению:
float total = 0;
for(float a = 0; a != 2; a += 0.01f) {
total += a;
}
Программист-новичок предполагает, что это суммирует каждое отдельное число в диапазоне 0, 0.01, 0.02, 0.03, ..., 1.97, 1.98, 1.99
, чтобы получить результат 199
- математически правильный ответ.
Происходят две вещи, которые делают это неверным:
- Программа, как написано, никогда не завершается.
a
никогда не становится равным2
, и цикл никогда не заканчивается. - Если мы перепишем логику цикла для проверки
a < 2
вместо этого, цикл завершится, но итоговое значение окажется чем-то отличным от199
. На совместимых с IEEE754 машинах он часто будет содержать примерно до201
.
Причина, по которой это происходит, заключается в том, что числа с плавающей запятой представляют собой приближенные значения их назначенных значений .
Классическим примером является следующее вычисление:
double a = 0.1;
double b = 0.2;
double c = 0.3;
if(a + b == c)
//This never prints on IEEE754-compliant machines
std::cout << "This Computer is Magic!" << std::endl;
else
std::cout << "This Computer is pretty normal, all things considered." << std::endl;
Хотя то, что мы, программист видим, это три числа, написанные в base10, то, что видят компилятор (и базовое оборудование), являются двоичными числами. Поскольку 0.1
, 0.2
и 0.3
требуют идеального деления на 10
что довольно легко в системе base-10, но невозможно в системе base-2 - эти цифры должны храниться в неточных форматах, подобно тому, как число 1/3
должен храниться в неточной форме 0.333333333333333...
в базе-10.
//64-bit floats have 53 digits of precision, including the whole-number-part.
double a = 0011111110111001100110011001100110011001100110011001100110011010; //imperfect representation of 0.1
double b = 0011111111001001100110011001100110011001100110011001100110011010; //imperfect representation of 0.2
double c = 0011111111010011001100110011001100110011001100110011001100110011; //imperfect representation of 0.3
double a + b = 0011111111010011001100110011001100110011001100110011001100110100; //Note that this is not quite equal to the "canonical" 0.3!