Страницы

Поиск по вопросам

четверг, 4 октября 2018 г.

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

Увидев этот код #include #include using namespace std; int main() { double d = 1000000000000000001; cout.setf(ios::fixed); cout.precision(0); //0 - число символов после точки cout << d << endl; printf("%.0lf
",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^-53, то есть около 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^-53. Ещё немного информации о числах с плавающей точкой: Хорошая статья на Хабре Классическая статья с теоретическими выкладками, но на английском. Если вам нужно представлять числа с высокой точностью, даже типа long double может не хватить. В этом случае, возможно, придётся применять числа бесконечной точности. Такие числа являются встроенными в некоторых языках (например, Java и C#), а для C и C++ есть хорошие библиотеки, предоставляющие такие числа. Я бы порекомендовал GMP

Комментариев нет:

Отправить комментарий