Вопиющая ошибка с плавающей запятой в программе C++

Я назначаю двойной литерал переменной 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?

stackoverflow.com/questions/21557816/… Я не проверял детали стандарта, но это может быть полезно.
Tiberiu Maran 30.05.2019 11:22

@HighPerformanceMark Меня действительно не волнует, что «вы продолжаете терять счет». Я ожидаю правильного ответа от исполняемого файла. Спасибо, вы не смогли мне помочь. Я поищу библиотеку, если она существует.

Emil Mocan 30.05.2019 11:52

@artcorpse Спасибо, кажется, добавление литерала суффикса не помогает.

Emil Mocan 30.05.2019 11:55
std::fixed, по-видимому, означает не то, что вы думаете.
Marc Glisse 30.05.2019 11:56

@MarcGlisse На самом деле мне нужно проанализировать строку и преобразовать ее в двойную. Код здесь должен подчеркнуть: я не могу точно представить буквальное «13000100000000000000144.5700788999» и ожидать от него правильной математики.

Emil Mocan 30.05.2019 12:02

Тогда ваша проблема в том, что вы понятия не имеете, что такое double (подсказка: это не BigFloat, конечно, вы не можете представлять все числа), пожалуйста, прочитайте документацию по этому поводу.

Marc Glisse 30.05.2019 12:04

@MarcGlisse Я знаю, что это не BigFloat или что-то в этом роде. Но я считаю глупым, что можно работать либо с очень большим числом, например 13000100000000000000144, либо с маленьким числом, например 0,5700788999, но не с 1300010000000000000144,5700788999. Я понял ограничения. Я просто нахожу это очень глупым. Просто мои 2 цента.

Emil Mocan 30.05.2019 12:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
7
161
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

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

80 бит с Visual Studio, вы уверены?

Marc Glisse 30.05.2019 11:48

@MarcGlisse У меня нет студии, на gcc x86_64 long double 80-битная.

Maxim Egorushkin 30.05.2019 11:50

@MaximEgorushkin Это, безусловно, интересное чтение. Я доживу до конца недели. Любое быстрое решение/обходной путь?

Emil Mocan 30.05.2019 11:56

Быстрое решение @EmilMocan — использовать более крупный тип, скажем, boost::mpfr_float (mpfr_float::default_precision(1000);, чтобы указать размер мантиссы). Или, если вы пытаетесь выполнить арифметику с фиксированной точкой, либо используйте тип, созданный для этого, либо какой-нибудь bigint.

Marc Glisse 30.05.2019 12:02

@EmilMocan Я не могу ничего порекомендовать для таких больших чисел, кроме использования математической библиотеки произвольной точности. Или попробуйте использовать числа, которые вписываются в double, long double.

Maxim Egorushkin 30.05.2019 12:02

Что сказал Эрик Постпишил. Как вы думаете, сколько десятичных цифр DBL_MAX? (Много.)

HolyBlackCat 30.05.2019 15:20

@HolyBlackCat Вы пропустили без потери точности. Некоторые более длинные номера могут быть сохранены, но обычно нет. double может хранить, например, DBL_MAX, но не DBL_MAX - 1.

Maxim Egorushkin 30.05.2019 15:24

@MaximEgorushkin gcc.godbolt.org имеет MSVC, и его легко найти в документации MSVC: Встроенные типы. MSVC уже давно использует IEEE-754 binary64 для long double и никогда не поддерживает 80-бит long double десятилетиями.

phuclv 14.05.2021 06:14

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

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

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

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

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

У меня больше всего баллов. Похоже, ответ заключается в использовании библиотеки BigMath. Большой плюс за то, что текст непринужденный и забавный. Спасибо.

Emil Mocan 30.05.2019 11:59

Другие вопросы по теме