Страницы

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

среда, 27 ноября 2019 г.

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

#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.

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

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