Десятичная и двойная скорость

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

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

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

Вопросы:

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

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


person Superman    schedule 30.11.2008    source источник
comment
В чем ваше узкое место? Где именно вы сжигаете все циклы процессора? Без надежных измерений есть хороший шанс, что вы смотрите на совершенно неправильную вещь.   -  person Jonathan Allen    schedule 04.12.2008
comment
Поскольку десятичные знаменатели - это степени 10, а двоичные знаменатели - степени двойки, даже 1 десятичный разряд не может быть представлен без ошибки. Например, 0,1 (одна десятая) не имеет точного эквивалента в двоичном формате, что является тем же принципом, что и одна треть, не имеющая точного представления в десятичном виде.   -  person Matt Curtis    schedule 28.07.2011


Ответы (7)


  1. Арифметика с плавающей запятой почти всегда будет значительно быстрее, потому что она поддерживается напрямую оборудованием. До сих пор почти ни одно широко используемое оборудование не поддерживает десятичную арифметику (хотя это меняется, см. Комментарии).
  2. Финансовые приложения должны всегда использовать десятичные числа, количество ужасных историй, связанных с использованием плавающей запятой в финансовых приложениях, бесконечно, вы сможете найти много таких примеров с помощью поиска Google.
  3. Хотя десятичная арифметика может быть значительно медленнее, чем арифметика с плавающей запятой, если вы не тратите значительное количество времени на обработку десятичных данных, влияние на вашу программу, вероятно, будет незначительным. Как всегда, сделайте соответствующее профилирование, прежде чем беспокоиться о разнице.
person Robert Gamble    schedule 30.11.2008
comment
+1: Избегайте преждевременной оптимизации. Сначала измерьте, а оптимизируйте только после того, как получите доказательства. - person S.Lott; 01.12.2008
comment
нет широко используемого оборудования ... Мэйнфреймы IBM (все еще очень популярные) имеют аппаратную десятичную систему. - person S.Lott; 01.12.2008
comment
Новые машины, поддерживающие новый стандарт с плавающей запятой IEEE 754, будут иметь аппаратную поддержку десятичной арифметики. Я считаю, что IBM Power6 - один из таких. - person Jonathan Leffler; 01.12.2008
comment
@ S.Lott: почти нет широко используемого оборудования. - person Robert Gamble; 01.12.2008
comment
@Jonathan: Я знаю, что IBM недавно объявила о поддержке десятичной дроби в Power6, и ожидается, что она будет интегрировать поддержку и в другие линии, другие могут последовать за ней, что хорошо, но пройдет много лет, прежде чем десятичная поддержка в оборудовании станет широко распространенной. - person Robert Gamble; 01.12.2008
comment
Я работал над системой, в которой производительность Decimal была узким местом. Это был ночной пакетный процесс на базе Windows. В 1996 г. - person sal; 16.07.2009

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

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

person Norman Ramsey    schedule 01.12.2008
comment
Какие здесь пределы умножения и деления? - person Joe; 05.03.2018
comment
Трудно поверить, что за это не проголосовали выше, так как это гораздо лучший ответ для OP, который стремился улучшить скорость на основе наблюдения за узкими местами в производительности. Масштабирование и удаление накипи - хороший ответ, который полностью избегает ошибки округления при увеличении скорости в 100 раз. - person Craig.Feied; 08.03.2018
comment
Это называется «Арифметика с фиксированной точкой». en.wikipedia.org/wiki/Fixed-point_arithmetic Ожидается дальнейшая вычислительная выгода сделано, если вы используете масштабные коэффициенты с основанием 2, а не с основанием 10, как вы это делали, а затем делите и умножайте на битовое дерьмо. Является ли этот прирост производительности существенным, конечно, зависит от вашего общего алгоритма и профилирования производительности. - person JimbobTheSailor; 22.09.2020

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

person Craig    schedule 30.11.2008
comment
+1: не тратьте время на производительность, если у вас нет доказательств того, что это математический пакет. - person S.Lott; 01.12.2008
comment
Это не ответ на вопросы ОП. - person Jim Balter; 19.04.2016

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

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

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

Информацию о десятичной арифметике и новом стандарте IEEE 754: 2008 можно найти на этом сайте Speleotrove.

person Jonathan Leffler    schedule 01.12.2008
comment
Для 1/100 нужен делитель 5, а также двоичная дробь, или, точнее, два делителя на пять: 1/100 = (1/4) * (1/5) * (1/5). 1/4 может быть точно представлена ​​в виде двоичного числа с плавающей запятой; фактор 1/5 не может. - person Jonathan Leffler; 01.12.2008
comment
@LawrenceDol Да, конечно, и никто не сказал иначе. Джонатан сделал правильно сказал, что они не могут быть точно представлены в двоичном формате. Надеюсь, за прошедшие годы ваше замешательство рассеялось. - person Jim Balter; 10.10.2018

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

person Community    schedule 03.05.2009
comment
можно подумать, что десятичный тип может быть реализован таким образом на сервере базы данных, обеспечивая конкурентоспособную производительность. - person philwalk; 23.08.2018

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

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

Теперь будет распечатано Верно!

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

person mezoid    schedule 01.12.2008
comment
Что действительно раздражает, так это то, что методы .NET ToString (), похоже, неспособны отобразить эту разницу, даже когда их просят отобразить до 20 десятичных цифр. Если вычесть числа, вы увидите, что между ними есть разница примерно в 4.44E-16 ... но я не вижу 4 нигде в строке 3.10000000000000000000, выводимой методом ToString. Если вы используете BitConverter.ToString (BitConverter.GetBytes (один + два)) и то же самое для (3.1), то байты действительно разные (CD-CC-CC-CC-CC-CC-08-40 vs. CC -CC-CC-CC-CC-CC-08-40), однако double.ToString () этого не показывает! - person Triynko; 22.04.2010
comment
Если используется правильное масштабирование и четко определенные правила округления, double может быть на каждый бит столь же точным, как decimal. Если нет точных правил округления, decimal часто будет иметь те же проблемы, что и double. Например, если три человека покупают сто предметов, которые продаются по цене 3 за 5 долларов, сколько денег должно быть получено? - person supercat; 05.06.2012
comment
Это не отвечает ни на один из вопросов, заданных OP. - person Jim Balter; 19.04.2016

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

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

person gbjbaanb    schedule 17.12.2008