Явная ошибка с плавающей точкой в ​​программе на C ++ - PullRequest
0 голосов
/ 30 мая 2019

Я назначаю двойной литерал двойной переменной. Значение переменной усекается, иначе я не могу понять, почему, например, разница diff равна 0,0.

Извините за дублирование кода на setprecision, но я действительно разозлился.

#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>

int main()
{
    long double d = 1300010000000000000144.5700788999;
    long double d1 = 1300010000000000000000.0;
    long double diff = d - d1; // shall be 144.5700788999!!!
    long double d2 = 0.5700788999;

    std::cout << "d = " << std::fixed << std::setprecision(std::numeric_limits<long double>::digits10 + 1) << d << '\n';
    std::cout << "d1 = " << std::fixed << std::setprecision(std::numeric_limits<long double>::digits10 + 1) << d1 << '\n';
    std::cout << "d - d1 = " << std::fixed << std::setprecision(std::numeric_limits<long double>::digits10 + 1) << diff << '\n';
    std::cout << "d2 = " << std::fixed << std::setprecision(std::numeric_limits<long double>::digits10 + 1) << d2 << '\n';
}

Это вывод:

d = 1300009999999999900000.0000000000000000
d1 = 1300009999999999900000.0000000000000000
d - d1 = 0.0000000000000000
d2 = 0.5700788999000001

Я ожидаю, что diff будет 144.5700788999, но это 0.0

Итак, как с этим бороться? (Окно 7 и выше, VS 2013)

... Использовать два типа double, один для высоких значений и один для низких значений? Например, вместо d используйте d1 и d2?

Ответы [ 2 ]

2 голосов
/ 30 мая 2019

80-бит long double (не уверен насчет его размера в MSVS) может хранить около 18 значащих десятичных цифр без потери точности.1300010000000000000144.5700788999 имеет 32 значащих десятичных знака и не может быть сохранено в точности как long double.

Чтение Количество цифр, необходимое для преобразования в оба конца для получения более подробной информации.

1 голос
/ 30 мая 2019

Ну, вы столкнулись с Диким Западом с плавающей точкой!Не доверяйте никому, не ожидайте многого, держите руку на ружье.

Дело в том, что представление с плавающей запятой - это раскол.Заданное количество байтов расходуется на хранение двух частей, значения мантиссы и десятой степени (это, конечно, упрощенное описание, однако этого здесь хватит).Если у вас есть значение, которое слишком велико, чтобы поместиться в мантиссе, что должен делать компьютер?Он должен переносить остаток в другую часть байтов (как это делают библиотеки Big Math) или просто округлять до ближайшего возможного значения.Позвольте мне показать:

d2 =                      0.5700788999; // shows                      0.5700788999000001
d2 = 1300010000000000000000.5700788999; // shows 1300009999999999934464.0000000000000000000

Эй, где моя дробная часть во втором случае?Это прошло!Вызовите полицию!Ой, подождите, это просто не вписывается ... Вот почему diff дает ноль: мантиссы настолько огромны, что хвостовая часть (там, где есть реальная разница) не может быть сохранена.И как только остальные цифры совпадают, мы получаем нулевую разницу.

После тщательного сравнения вы можете заметить еще одну вещь: напечатанное значение близко к назначенному, однако оно немного отличается.Это потому, что мантисса - это просто сумма степеней 2. Таким образом, чтобы представить значение, компьютер должен округлить присвоенное значение до ближайшего двоично-совместимого значения.Иногда это другой вид боли, и вам не следует сравнивать числа с плавающей запятой с помощью оператора равенства, просто оцените разницу и сравните ее с ожидаемой дельтой предполагаемой точности.

...