Десятичная или двойная скорость - PullRequest
47 голосов
/ 01 декабря 2008

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

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

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

Вопросы:

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

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

Ответы [ 7 ]

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

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

Что касается точного представления, вы правы быть осторожными, потому что точная десятичная дробь, такая как 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-битный целочисленный тип. Что будет быстрее, зависит от вашей аппаратной платформы.

20 голосов
/ 01 декабря 2008

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

12 голосов
/ 01 декабря 2008
  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 на этом сайте Speleotrove .

8 голосов
/ 03 мая 2009

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

5 голосов
/ 01 декабря 2008

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

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

double one = 3.05;
double two = 0.05;

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

Этот бит кода будет распечатан False , хотя 3.1 равен 3.1 ...

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

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

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

Теперь это напечатает True !

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

1 голос
/ 17 декабря 2008

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

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

...