Арифметическая ошибка с двойным c++

Я заметил небольшую ошибку в некоторых арифметических вычислениях с использованием double. Это действительно странно, всегда есть небольшая ошибка и/или лишняя значащая цифра.

Сначала я использую atof для преобразования числа, состоящего из двух значащих цифр, которые я читаю из текстового файла (затем я записываю их в вектор):

 // Puts into vector
  double ask_file, bid_file; // Values of ask and bid from file
  double cur_conversion = 0.16;
  ask_file = cur_conversion*atof(values[0].c_str()); 
  bid_file = cur_conversion*atof(values[1].c_str()); 

Затем я делаю арифметику (из другого класса, два разных объекта):

diff = OKC->bid_val() - BV->ask_val(); // diff
diff2 = OKC->ask_val() - BV->bid_val(); // diff2

Это результат:

BV Askfile: 245.267 Bidfile: 245.078 
OKC Askfile: 248.82 Bidfile: 248.73 
diff: 3.4628 diff2: 3.7416

Как видите, в обоих вычислениях есть ошибка. diff = 3,463, а НЕ 3,4628. И diff2 = 3,742, а НЕ 3,7416.

Вы знаете, что происходит??


person Luis Cruz    schedule 14.02.2015    source источник
comment
docs.oracle.com/cd/E19957-01/806- 3568/ncg_goldberg.html хорошо читается   -  person Ed Heal    schedule 14.02.2015
comment
Как вы печатаете эти значения? Не похоже, что вы печатаете их с максимальной точностью, поэтому я предполагаю, что значения BV действительно (ближе к) 245,2672 и 245,0784, что будет 0,16 * 1531,74 и 0,16 * 1532,92 соответственно. Я также предполагаю, что это абсолютные суммы денег, и в этом случае вы, возможно, захотите пересмотреть выполнение этих вычислений с числами с плавающей запятой.   -  person Wintermute    schedule 14.02.2015
comment
Не используйте арифметику с плавающей запятой в финансовых инструментах. Ожидается, что он будет нести и накапливать ошибку из-за формы представления значения.   -  person Valeri Atamaniouk    schedule 14.02.2015
comment
@ValeriAtamaniouk: на самом деле нет ничего плохого в использовании арифметики с плавающей запятой при работе с финансовыми инструментами. Однако плавающие точки должны использовать основание 10, а не основание 2, которое используется для встроенных типов float, double или long double.   -  person Dietmar Kühl    schedule 14.02.2015
comment
Что вы тогда порекомендуете для финансовых расчетов? Является ли float лучше, чем double? В противном случае, как лучше всего выполнять эти вычисления без какой-либо арифметики с плавающей запятой?   -  person Luis Cruz    schedule 14.02.2015
comment
@LuisCruz Используйте арифметику с фиксированной точкой или произвольную точность, таким образом контролируя все ошибки вычислений так, как вам нужно.   -  person Valeri Atamaniouk    schedule 14.02.2015
comment
То, что говорит Валерий Атаманюк, является отраслевым стандартом. Все форматы с плавающей запятой почти всегда не подходят для финансов. Вы хотите использовать что-то целочисленное для представления тысячных долей цента или что-то подобное.   -  person Drew Dormann    schedule 14.02.2015
comment
Каковы были первоначальные входы? Пробовали ли вы печатать с более высокой точностью, чтобы увидеть, что происходит? Ошибки в четвертой или пятой значащей цифре не являются стандартной проблемой ошибки округления, учитывая короткую серию двойных операций над числами примерно одинаковой величины.   -  person Patricia Shanahan    schedule 15.02.2015
comment
Не используйте числа с плавающей запятой для валюты. Используйте целые числа. Люди думают, что если счет закончился на копейки, то что происходит с фунтами   -  person Ed Heal    schedule 15.02.2015


Ответы (1)


Проблема в том, что в общем случае невозможно точно представить дробные десятичные значения с помощью двоичных чисел с плавающей запятой. Например, 0.1 представляется как 1.000000000000000055511151231257827021181583404541015625E-1 при использовании double (вы можете использовать это онлайн анализатор для определения значений). При вычислении с этими округленными значениями количество необходимых двоичных цифр превысит количество, которое может быть представлено, и значение будет дополнительно округлено, что приведет к большей ошибке. Конечно, все это описано в документе Голдберга с указанием по комментарию Эда Хила.

Существует ряд альтернативных представлений, которые вы можете использовать для точного вычисления с десятичными значениями. Если представление не использует представление произвольного размера, оно будет точно только в пределах некоторого диапазона значений. Типичные варианты:

  1. Использование большого целочисленного представления вместе с подходящим десятичным масштабированием.
  2. Строки (или BCD) цифр.
  3. Представление с фиксированной запятой, которое в основном представляет собой просто целое число вместе с фиксированным десятичным показателем степени, где показатель степени является неявным в типе с фиксированной точкой (или, например, в аргументе шаблона).
  4. Вместо использования двоичной с плавающей запятой вы должны использовать десятичную с плавающей запятой. Плавающие запятые — это просто представление знака, мантиссы и показатель степени со значением, вычисляемым как (-1) знак * значимый * основаниепоказатель степени. double использует базу 2, но для десятичных вычислений вы должны использовать базу 10.
  5. Используя два больших целых числа, вы можете представить значение как рациональное число.
  6. Есть несколько других вариантов, но приведенный выше список — это то, что я считаю практичными вариантами.

В зависимости от выбора реализации различные операции более или менее легко реализовать, и конкретные операции различаются. Например, за исключением представления с использованием рациональных операций деления всегда будут округляться, когда делитель не может быть представлен как произведение 2 и 5.

Какое представление лучше всего подойдет для вашего приложения, зависит от ваших потребностей. Если у вас есть только торговые цены в диапазонах, типичных для акций, может работать представление с фиксированной точкой. Если вам нужно охватить все виды значений, с которыми вы можете столкнуться в финансах, например, государственные долги, а также процентные ставки, вам потребуется более 64 бит для представления с фиксированной запятой, а десятичное представление с плавающей запятой может быть лучшим представлением. В зависимости от того, нужно ли вам передавать и/или хранить значения, представление фиксированного размера может не потребоваться, и в этом случае другие представления могут быть разумным выбором.

person Dietmar Kühl    schedule 14.02.2015
comment
Вы уверены, что в этом случае проблема именно в этом? Ошибка, по-видимому, находится примерно в четвертой или пятой значащей цифре после выполнения вычислений с аналогичными значениями. - person Patricia Shanahan; 15.02.2015
comment
@PatriciaShanahan: да, я почти уверен, что это проблема, хотя вы не получите ошибку из напечатанных значений напрямую. Представленные значения могут на самом деле сильно отличаться при печати, как указано выше. Например, double d0(248.7298) будет также печататься как 248.73 (при настройках по умолчанию) и даст наблюдаемый результат (3.4628) при вычитании 245.267. - person Dietmar Kühl; 15.02.2015
comment
Я подозреваю, что вы описываете в комментарии, и ОП может исправить это, просто напечатав больше цифр. Та же проблема могла возникнуть из-за слишком агрессивного округления вывода, даже если арифметика выполнялась в действительных числах. Это сильно отличается от неотъемлемых ограничений двойной точности, которые могут потребовать более радикальных исправлений в вашем ответе. - person Patricia Shanahan; 15.02.2015