Десятичная и двойная скорость
Я пишу финансовые приложения, где постоянно борюсь за решение использовать двойное и десятичное число.
Вся моя математика работает с числами не более 5 знаков после запятой и не превышает ~ 100 000. У меня такое ощущение, что все они могут быть представлены как двойники в любом случае без ошибок округления, но никогда не были уверены.
Я бы продолжил и сделал переход от десятичных знаков к удвоениям для очевидного преимущества скорости, за исключением того, что в конце дня я все еще использую метод ToString для передачи цен на биржи и должен убедиться, что он всегда выводит число, которое я ожидаю. (89,99 вместо 89,99000000001)
Вопросы:
- Является ли преимущество скорости действительно таким же большим, как предполагают наивные тесты? (~ 100 раз)
- Есть ли способ гарантировать, что вывод из ToString будет тем, что я хочу? Это подтверждается тем фактом, что мое число всегда представимо?
ОБНОВЛЕНИЕ: мне нужно обработать ~ 10 миллиардов обновлений цен до того, как мое приложение сможет запускаться, и я применил его с десятичной точностью прямо по очевидным соображениям защиты, но для включения требуется ~ 3 часа, удвоения резко сократят мои время включения. Есть ли безопасный способ сделать это с помощью парных?
Ответы
Ответ 1
- Арифметика с плавающей точкой почти всегда будет значительно быстрее, потому что она поддерживается непосредственно аппаратным обеспечением. До сих пор почти нет широко используемого оборудования, поддерживающего десятичную арифметику (хотя это меняется, см. Комментарии).
- Финансовые приложения должны всегда использовать десятичные числа, количество историй ужасов, связанных с использованием с плавающей запятой в финансовых приложениях, бесконечно, вы можете найти много таких примеров с поиском Google. li >
- Хотя десятичная арифметика может быть значительно медленнее, чем арифметика с плавающей запятой, если вы не тратите значительное количество времени на обработку десятичных данных, воздействие на вашу программу, вероятно, будет незначительным. Как всегда, сделайте соответствующее профилирование, прежде чем вы начнете беспокоиться о различии.
Ответ 2
Здесь есть две разделимые проблемы. Один из них заключается в том, имеет ли двойник достаточную точность для хранения всех необходимых вам битов, а другой - там, где он может точно представлять ваши номера.
Что касается точного представления, вы правы, чтобы быть осторожным, потому что точная десятичная дробь, такая как 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, вы также можете попробовать масштабированную целочисленную арифметику, используя int64_t
64-битный целочисленный тип. Что быстрее будет зависеть от вашей аппаратной платформы.
Ответ 3
Всегда используйте Decimal для любых финансовых расчетов, или вы будете навсегда преследовать ошибки округления 1 цент.
Ответ 4
- Да; Арифметика программного обеспечения действительно в 100 раз медленнее, чем аппаратное обеспечение. Или, по крайней мере, это намного медленнее, а коэффициент 100, на порядок или порядка, примерно соответствует. Назад в плохие старые дни, когда вы не могли предположить, что каждый 80386 имел 80387 с плавающей запятой, тогда у вас тоже было программное обеспечение для моделирования двоичной с плавающей запятой, и это было медленным.
- Нет; вы живете в фантастической стране, если считаете, что чистая двоичная плавающая точка может когда-либо точно представлять все десятичные числа. Двоичные числа могут сочетать половинки, четверти, восьмые и т.д., Но так как точное десятичное число 0,01 требует двух факторов одной пятой и одного коэффициента в одну четверть (1/100 = (1/4) * (1/5) * (1/5)), и поскольку одна пятая не имеет точного представления в двоичном выражении, вы не можете точно представить все десятичные значения с двоичными значениями (поскольку 0.01 является контр-примером, который не может быть представлен точно, но представляет собой огромный класс десятичных чисел, не могут быть представлены точно).
Итак, вам нужно решить, можете ли вы иметь дело с округлением перед вызовом ToString() или вам нужно найти какой-то другой механизм, который будет обрабатывать округление результатов, когда они преобразуются в строку. Или вы можете продолжать использовать десятичную арифметику, поскольку она будет оставаться точной, и она будет быстрее, как только выпущены машины, которые поддерживают новую десятичную арифметику IEEE 754 в аппаратном обеспечении.
Обязательная перекрестная ссылка: Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой. Это один из многих возможных URL-адресов и ведет к PDF файлу. Там есть версия HTML на Sun, которая, по-видимому, является отредактированной версией той же самой статьи.
Информация о десятичной арифметике и новом стандарте IEEE 754: 2008 на этом сайте Speleotrove (материал, ранее размещенный в IBM).
Ответ 5
Просто используйте длинный и умножьте на мощность 10. После того, как вы закончите, разделите на ту же мощность 10.
Ответ 6
Десятичные суммы всегда должны использоваться для финансовых расчетов. Размер цифр не важен.
Самый простой способ объяснить мне через код С#.
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!
Если вы хотите избежать такого рода проблем, я рекомендую вам придерживаться десятичных знаков.
Ответ 7
Я отсылаю вас к моему ответу на этот вопрос.
Используйте длинный, сохраняйте наименьшую сумму, которую нужно отслеживать, и отображайте значения соответственно.