Вопрос

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

Вся моя математика работает с числами, имеющими не более 5 десятичных знаков и не превышающими ~100 000.У меня такое ощущение, что все это в любом случае можно представить в виде двойных чисел без ошибки округления, но я никогда не был в этом уверен.

Я бы пошел дальше и переключился с десятичных чисел на двойные для очевидного преимущества в скорости, за исключением того, что в конце дня я все еще использую метод ToString для передачи цен на биржи, и мне нужно убедиться, что он всегда выводит число, которое я ожидать.(89,99 вместо 89,99000000001)

Вопросы:

  1. Действительно ли преимущество в скорости настолько велико, как предполагают наивные тесты?(~100 раз)
  2. Есть ли способ гарантировать, что вывод ToString будет тем, что я хочу?Гарантируется ли это тем, что мое число всегда представимо?

ОБНОВЛЯТЬ:Мне нужно обработать ~ 10 миллиардов обновлений цен, прежде чем мое приложение сможет запуститься, и я внедрил десятичную систему прямо сейчас по очевидным причинам защиты, но для включения требуется ~ 3 часа, двойные значения значительно сократят время моего включения.Есть ли безопасный способ сделать это с дублями?

Это было полезно?

Решение

  1. Арифметика с плавающей запятой почти всегда будет значительно быстрее, поскольку она поддерживается непосредственно аппаратным обеспечением.На данный момент почти ни одно широко используемое оборудование не поддерживает десятичную арифметику (хотя ситуация меняется, см. комментарии).
  2. Финансовые приложения должны всегда используйте десятичные числа, количество ужасных историй, связанных с использованием чисел с плавающей запятой в финансовых приложениях, бесконечно, вы сможете найти много таких примеров с помощью поиска в Google.
  3. Хотя десятичная арифметика может быть значительно медленнее, чем арифметика с плавающей запятой, если вы не тратите значительное количество времени на обработку десятичных данных, влияние на вашу программу, вероятно, будет незначительным.Как всегда, проведите соответствующее профилирование, прежде чем начинать беспокоиться о разнице.

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

Здесь есть две отдельные проблемы.Во-первых, имеет ли двойная точность достаточную точность, чтобы хранить все необходимые вам биты, а во-вторых, может ли она точно представлять ваши числа.

Что касается точного представления, вы правы: будьте осторожны, потому что точная десятичная дробь, такая как 1/10, не имеет точного двоичного аналога.Однако, если вы знаете, что вам нужна точность всего 5 десятичных цифр, вы можете использовать масштабированный арифметика, в которой вы оперируете числами, умноженными на 10^5.Например, если вы хотите точно представить число 23,7205, вы представляете его как 2372050.

Посмотрим, достаточно ли точности:двойная точность дает вам 53 бита точности.Это эквивалентно точности более 15 десятичных цифр.Таким образом, это позволит вам использовать пять цифр после десятичной точки и 10 цифр перед десятичной точкой, что кажется достаточным для вашего приложения.

Я бы поместил этот код C в файл .h:

typedef double scaled_int;

#define SCALE_FACTOR 1.0e5  /* number of digits needed after decimal point */

static inline scaled_int adds(scaled_int x, scaled_int y) { return x + y; }
static inline scaled_int muls(scaled_int x, scaled_int y) { return x * y / SCALE_FACTOR; }

static inline scaled_int scaled_of_int(int x) { return (scaled_int) x * SCALE_FACTOR; }
static inline int intpart_of_scaled(scaled_int x) { return floor(x / SCALE_FACTOR); }
static inline int fraction_of_scaled(scaled_int x) { return x - SCALE_FACTOR * intpart_of_scaled(x); }

void fprint_scaled(FILE *out, scaled_int x) {
  fprintf(out, "%d.%05d", intpart_of_scaled(x), fraction_of_scaled(x));
}

Вероятно, есть несколько трудных моментов, но для начала этого должно быть достаточно.

Никаких накладных расходов на сложение, стоимость умножения или деления удваивается.

Если у вас есть доступ к C99, вы также можете попробовать масштабированную целочисленную арифметику, используя int64_t 64-битный целочисленный тип.Что быстрее, будет зависеть от вашей аппаратной платформы.

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

  1. Да;программная арифметика действительно в 100 раз медленнее аппаратной.Или, по крайней мере, это намного медленнее, и коэффициент 100, плюс-минус, на порядок, вполне верен.В старые добрые времена, когда вы не могли предположить, что каждый 80386 имеет сопроцессор с плавающей запятой 80387, тогда у вас также было программное моделирование двоичной плавающей запятой, и это было медленно.
  2. Нет;вы живете в стране фантазий, если думаете, что чисто двоичная плавающая запятая может когда-либо точно представлять все десятичные числа.Двоичные числа могут объединять половины, четверти, восьмые части и т. д., но поскольку для точного десятичного числа 0,01 требуются два множителя в одну пятую и один множитель в одну четверть (1/100 = (1/4)*(1/5)*(1 /5)) и поскольку одна пятая не имеет точного представления в двоичном виде, вы не можете точно представить все десятичные значения двоичными значениями (поскольку 0,01 — это контрпример, который невозможно представить точно, но он представляет огромный класс десятичных чисел, которые точно представить невозможно).

Итак, вам нужно решить, сможете ли вы справиться с округлением перед вызовом ToString() или вам нужно найти какой-то другой механизм, который будет округлять ваши результаты при их преобразовании в строку.Или вы можете продолжать использовать десятичную арифметику, поскольку она останется точной и станет быстрее, как только будут выпущены машины, поддерживающие новую десятичную арифметику IEEE 754 на аппаратном уровне.

Обязательная перекрестная ссылка: Что должен знать каждый ученый-компьютерщик об арифметике с плавающей запятой.Это один из многих возможных URL-адресов.

Информация о десятичной арифметике и новом стандарте IEEE 754:2008 здесь. Спелеотров сайт.

Просто используйте long и умножьте на 10.После того, как вы закончите, разделите результат на ту же степень 10.

Для финансовых расчетов всегда следует использовать десятичные дроби.Размер цифр не имеет значения.

Самый простой способ объяснить это с помощью кода C#.

double one = 3.05;
double two = 0.05;

System.Console.WriteLine((one + two) == 3.1);

Этот фрагмент кода будет распечатан ЛОЖЬ хотя 3,1 равно 3,1...

То же самое... но с использованием десятичной дроби:

decimal one = 3.05m;
decimal two = 0.05m;

System.Console.WriteLine((one + two) == 3.1m);

Теперь это будет распечатано Истинный!

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

Я отсылаю вас к моему ответу, данному этот вопрос.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top