#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.
Комментариев нет:
Отправить комментарий