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

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

Вся моя математика работает с числами не более 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

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

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