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

Я заметил небольшую ошибку в некоторых арифметических вычислениях с использованием 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.

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

1 ответ

Решение

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

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

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

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

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

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