2008-11-30 2 views
43

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

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

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

Вопросы:

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

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

+4

Поскольку десятичные знаменатели силы 10, и бинарные знаменатели являются степенями 2, даже 1 десятичное место не может быть представлено без ошибок. Например, 0,1 (одна десятая) не имеет точного эквивалента в двоичном формате, что является тем же принципом, что и одна треть, не имеющая точного представления в десятичной системе. – 2011-07-28 00:22:54

+1

Какое у вас узкое место? Точно, где вы сжигаете все свои циклы процессора? Без твердых измерений есть хорошая чакне, вы смотрите на совершенно неправильную вещь. – 2008-12-04 07:59:36

ответ

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

+1: Избегайте преждевременной оптимизации. Сначала измерьте, оптимизируйте только после того, как у вас есть доказательства. – 2008-12-01 00:44:57

+0

«нет широко используемого оборудования ...». Мейнфреймы IBM (по-прежнему чрезвычайно популярны) имеют аппаратное десятичное число. – 2008-12-01 00:45:55

+3

Новые машины, поддерживающие новый стандарт с плавающей точкой IEEE 754, будут иметь десятичную арифметическую поддержку в оборудовании. Я полагаю, что IBM Power6 - одна из таких. – 2008-12-01 00:49:14

17

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

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

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

Обязательная перекрестная ссылка: What Every Computer Scientist Should Know About Floating-Point Arithmetic. Это один из многих возможных URL-адресов и ведет к PDF-файлу. Существует версия HTML на Sun, которая, по-видимому, является отредактированной версией той же самой статьи.

Информация о десятичной арифметике и новом стандарте IEEE 754: 2008 на этом сайте Speleotrove (материал, ранее размещенный в IBM).

5

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

Самый простой способ объяснить мне через код 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); 

Это теперь будет распечатать True!

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

21

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

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

1

Я отсылаю вас к моему ответу this question.

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

7

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