#double #c #вывод #c++
Увидев этот код
#include
#include
using namespace std;
int main()
{
double d = 1000000000000000001;
cout.setf(ios::fixed);
cout.precision(0); //0 - число символов после точки
cout << d << endl;
printf("%.0lf\n",d);
return 0;
}
Неожиданно увидел в результате (http://ideone.com/1SEHpO)
1000000000000000000
1000000000000000000
Куда делась 1?
Из за чего так происходит? Особенности записи типов с плавающей точкой?
Ответы
Ответ 1
У типа double ограниченная точность, поэтому в нём невозможно различить 1000000000000000000
и 1000000000000000001. Если вы объявите
double d1 = 1000000000000000001;
double d2 = 1000000000000000000;
— то d1 и d2 будут одним и тем же числом. Пруф: http://ideone.com/WqQSAd
Проблема в том, что числа с плавающей точкой не бесконечно точны. Для типа double,
например, выделяется 52 бита под значащие цифры (и ещё 11 бит под показатель степени),
число внутри хранится как бы закодированным в виде CCCCCCC * 2^PPPP (C — значащие цифры,
P — степень). А значит, числа, которые можно представить в виде double, расположены
с некоторым шагом (который зависит от величины порядка): числа, расположенные «между»
представимыми числами выразить точно с помощью double вообще нельзя, и они автоматически
округляются до ближайшего представимого числа. Пример такого числа — 0.1: оно не выражается
(двоичной) дробью, и константа 0.1 внутри хранится как приблизительно
0.1000000000000000055511151231257827021181583404541015625
Максимальное значение, которое можно прибавить к единице так, чтобы она не изменилась,
называется машинным эпсилоном. Для типа double машинный эпсилон равен, очевидно, 2⁻⁵³,
то есть около 1.11e-16.
Почему очевидно? Потому что для единицы значащие биты такие: 10000000...000, а значит,
следующее по величине число, которое можно выразить типом double, должно иметь значащие
цифры 10000000...001. (На самом деле чуть-чуть сложнее: ведущая единица не хранится,
а подразумевается.)
Отсюда выплывает, что 1 + 1e-16 для типа double неотличимо от 1. Поскольку для бóльших
чисел увеличивается порядок, при увеличении первого слагаемого машинный эпсилон приблизительно
пропорционально увеличивается. Соответственно, 1e16 + 1 будет равно 1e16. В вашем же
случае вы на два порядка выше предела: вы прибавляете к 1e18.
Давайте ещё проэкспериментируем: http://ideone.com/4XJxFj
1 + 1.110223024625156e-16 == 1
но уже
1 + 1.110223024625157e-16 != 1
Это потому, что 1.110223024625156e-16 — приближение к 2⁻⁵³.
Ещё немного информации о числах с плавающей точкой:
Хорошая статья на Хабре
Классическая статья с теоретическими выкладками, но на английском.
Если вам нужно представлять числа с высокой точностью, даже типа long double может
не хватить. В этом случае, возможно, придётся применять числа бесконечной точности.
Такие числа являются встроенными в некоторых языках (например, Java и C#), а для C
и C++ есть хорошие библиотеки, предоставляющие такие числа. Я бы порекомендовал GMP.