Вывод числа double (10^18)+1

1,00
р.
Увидев этот код
#include #include using namespace std int main() { double d = 1000000000000000001 cout.setf(ios::fixed) cout.precision(0) //0 - число символов после точки cout << d << endl printf("%.0lf<br>",d) return 0 }
Неожиданно увидел в результате (http://ideone.com/1SEHpO)
1000000000000000000 1000000000000000000

Куда делась 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.