Я назначаю двойной литерал переменной double
.
Значение переменной усекается, иначе я не могу понять, почему, например, разница 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
Итак, как с этим бороться? (Window 7 и выше, VS 2013)
...Чтобы использовать два двойника, один для высоких значений и один для низких значений?
Например, вместо использования d
использовать d1
и d2
?
@HighPerformanceMark Меня действительно не волнует, что «вы продолжаете терять счет». Я ожидаю правильного ответа от исполняемого файла. Спасибо, вы не смогли мне помочь. Я поищу библиотеку, если она существует.
@artcorpse Спасибо, кажется, добавление литерала суффикса не помогает.
std::fixed
, по-видимому, означает не то, что вы думаете.
@MarcGlisse На самом деле мне нужно проанализировать строку и преобразовать ее в двойную. Код здесь должен подчеркнуть: я не могу точно представить буквальное «13000100000000000000144.5700788999» и ожидать от него правильной математики.
Тогда ваша проблема в том, что вы понятия не имеете, что такое double
(подсказка: это не BigFloat, конечно, вы не можете представлять все числа), пожалуйста, прочитайте документацию по этому поводу.
@MarcGlisse Я знаю, что это не BigFloat или что-то в этом роде. Но я считаю глупым, что можно работать либо с очень большим числом, например 13000100000000000000144, либо с маленьким числом, например 0,5700788999, но не с 1300010000000000000144,5700788999. Я понял ограничения. Я просто нахожу это очень глупым. Просто мои 2 цента.
80-битный long double
(не уверен насчет его размера в MSVS) может хранить около 18 значащих десятичных цифр без потери точности. 1300010000000000000144.5700788999
имеет 32 значащих десятичных цифры и не может храниться точно так же, как long double
.
Прочтите Количество цифр, необходимое для конвертации туда и обратно для более подробной информации.
80 бит с Visual Studio, вы уверены?
@MarcGlisse У меня нет студии, на gcc x86_64 long double
80-битная.
@MaximEgorushkin Это, безусловно, интересное чтение. Я доживу до конца недели. Любое быстрое решение/обходной путь?
Быстрое решение @EmilMocan — использовать более крупный тип, скажем, boost::mpfr_float (mpfr_float::default_precision(1000);
, чтобы указать размер мантиссы). Или, если вы пытаетесь выполнить арифметику с фиксированной точкой, либо используйте тип, созданный для этого, либо какой-нибудь bigint.
@EmilMocan Я не могу ничего порекомендовать для таких больших чисел, кроме использования математической библиотеки произвольной точности. Или попробуйте использовать числа, которые вписываются в double
, long double
.
Что сказал Эрик Постпишил. Как вы думаете, сколько десятичных цифр DBL_MAX
? (Много.)
@HolyBlackCat Вы пропустили без потери точности. Некоторые более длинные номера могут быть сохранены, но обычно нет. double
может хранить, например, DBL_MAX
, но не DBL_MAX - 1
.
@MaximEgorushkin gcc.godbolt.org имеет MSVC, и его легко найти в документации MSVC: Встроенные типы. MSVC уже давно использует IEEE-754 binary64 для long double
и никогда не поддерживает 80-бит long double
десятилетиями.
Что ж, вы столкнулись с Диким Западом плавающих точек! Не верь никому, не жди многого, держи руку на пульсе.
Дело в том, что представление с плавающей запятой — это разделение. Заданное количество байт тратится на хранение двух частей, значения мантиссы и десятой степени (конечно, это упрощенное описание, однако здесь его будет достаточно). Если у вас есть значение, которое слишком велико для мантиссы, что должен сделать компьютер? Он должен перенести остаток в другую часть байтов (как это делают библиотеки Big Math) или просто округлить до ближайшего возможного значения. Позволь мне показать:
d2 = 0.5700788999; // shows 0.5700788999000001
d2 = 1300010000000000000000.5700788999; // shows 1300009999999999934464.0000000000000000000
Эй, где моя дробная часть во 2-м случае? Его больше нет! Вызовите полицию! Ой, подождите, это просто не вписывается в... Вот почему diff дает ноль: богомолы настолько огромны, что хвостовую часть (там, где собственно разница) нельзя сохранить. И как только остальные цифры совпадают, у нас нулевая разница.
После внимательного сравнения можно заметить еще одну вещь: напечатанное значение близко к заданному, но немного отличается. Это связано с тем, что мантисса — это просто сумма степеней двойки. Итак, чтобы представить значение, компьютер должен округлить присвоенное значение до ближайшего двоично-совместимого. Иногда это другой вид боли, и вы не должны сравнивать числа с плавающей запятой с помощью оператора равенства, просто оценивайте разницу и сравнивайте ее с ожидаемой дельтой предполагаемой точности.
У меня больше всего баллов. Похоже, ответ заключается в использовании библиотеки BigMath. Большой плюс за то, что текст непринужденный и забавный. Спасибо.