Арифметическая ошибка с двойным с ++
Я заметил небольшую ошибку в некоторых арифметических вычислениях с использованием 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
(Вы можете использовать этот онлайн-анализатор для определения значений). При вычислении с этими округленными значениями количество необходимых двоичных цифр будет превышать число, которое может быть представлено, и значение будет дополнительно округлено, что приведет к большей ошибке. Конечно, все это освещено в статье Гольдберга, на которую указывает комментарий Эда Хила.
Существует ряд альтернативных представлений, которые вы можете использовать для точного вычисления с десятичными значениями. Если представление не использует представление произвольного размера, оно будет точно только в некотором диапазоне значений. Типичные варианты:
- Использование большого целочисленного представления вместе с подходящим десятичным масштабированием.
- Строки (или BCD) цифр.
- Представление с фиксированной точкой, которое в основном представляет собой целое число вместе с фиксированным десятичным показателем, где показатель неявен в типе с фиксированной точкой (или, например, в качестве аргумента шаблона).
- Вместо использования двоичной плавающей запятой вы должны использовать десятичные числа с плавающей запятой. Плавающие точки - это просто представление знака, значимого и показателя степени, значение которого вычисляется как (-1)знак * значимый и * базовыйпоказатель.
double
использует базу2
но для десятичных вычислений вы бы использовали базу10
, - Используя два больших целых числа, вы можете представить значение как рациональное число.
- Есть несколько других вариантов, но приведенный выше список - это то, что я считаю практическими вариантами.
В зависимости от выбора реализации различные операции более или менее просты в реализации, а точные операции различаются. Например, за исключением представления с использованием рациональных операций деления всегда будут округлены, когда делитель не может быть представлен как произведение 2
а также 5
,
Какое представление будет работать лучше для вашего приложения, зависит от ваших потребностей. Если у вас есть только торговые цены в диапазонах, типичных для акций, может работать представление с фиксированной точкой. Если вам необходимо охватить все виды значений, с которыми вы можете столкнуться в финансах, например, национальные долги, а также процентные ставки, вам потребуется более 64 бит для представления с фиксированной запятой, и представление с десятичной плавающей запятой может быть лучшим представлением. В зависимости от того, нужно ли вам передавать и / или сохранять значения, представление с фиксированным размером может не потребоваться, и в этом случае другие представления могут быть разумным выбором.