Удвойте мои деньги: моя структура использует двойные для денежных сумм - PullRequest
9 голосов
/ 06 октября 2010

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

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

Фреймворк ORM также обрабатывает извлечение значений из базы данных и ее хранение. В базе данных денежными значениями являются номера типа (19, 7), но платформа ORM отображает их в двойные числа.

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

Редактировать: да, я знаю, BigDecimal следует использовать. Проблема в том, что я тесно связан с фреймворком, в котором, например, класс framework.commerce.pricing.ItemPriceInfo имеет члены double mRawTotalPrice; и двойная цена Собственный код приложения моей компании расширяется, например, этот ItemPriceInfoClass.

На самом деле, я не могу сказать своей компании: «Потратьте два года работы и потратили сотни тысяч долларов, основываясь на коде этой платформы, из-за ошибок округления»

Ответы [ 5 ]

10 голосов
/ 06 октября 2010

Если допустимо, относитесь к денежному типу как к целому. Другими словами, если вы работаете в США, отслеживайте центы вместо долларов, если центы обеспечивают необходимую гранулярность. Двойные числа могут точно представлять целые числа до очень большое значение (2 ^ 53) (без ошибок округления до этого значения).

Но на самом деле, правильное решение - полностью обойти фреймворк и использовать что-то более разумное. Это такая любительская ошибка для фреймворка - кто знает, что еще скрывается?

6 голосов
/ 06 октября 2010

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

Вот некоторая информация о double vs BigDecimal .Этот пост предлагает использовать BigDecimal, хотя он медленнее.

3 голосов
/ 06 октября 2010

Множество людей предложат использовать BigDecimal, и если вы не знаете, как использовать округления в вашем проекте, это то, что вам следует делать.

Если вы знаете, как правильно использовать десятичное округление, используйте double. Его на много порядков быстрее, намного понятнее и проще и, следовательно, меньше подвержено ошибкам ИМХО. Если вы используете доллары и центы (или вам нужны два десятичных знака), вы можете получить точный результат для значений до 70 триллионов долларов.

Как правило, вы не получите ошибок округления, если исправите их с помощью округления.

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

РЕДАКТИРОВАТЬ: рассмотрим этот простой пример ошибки округления.

    double a = 100000000.01;
    double b = 100000000.09;
    System.out.println(a+b); // prints 2.0000000010000002E8

Существует несколько возможных стратегий округления. Вы можете округлить результат при печати / просмотре. например,

    System.out.printf("%.2f%n", a+b); // prints 200000000.10

или математически округлить результат

    double c = a + b;
    double r= (double)((long)(c * 100 + 0.5))/100;
    System.out.println(r); // prints 2.000000001E8

В моем случае я округляю результат при отправке с сервера (запись в сокет и файл), но использую собственную процедуру, чтобы избежать создания какого-либо объекта.

Более общая функция округления выглядит следующим образом, но если вы можете использовать printf или DecimalFormat, может быть проще.

private static long TENS[] = new long[19]; static { 
    TENS[0] = 1; 
    for (int i = 1; i < TENS.length; i++) TENS[i] = 10 * TENS[i - 1]; 
} 

public static double round(double v, int precision) { 
    assert precision >= 0 && precision < TENS.length; 
    double unscaled = v * TENS[precision]; 
    assert unscaled > Long.MIN_VALUE && unscaled < Long.MAX_VALUE; 
    long unscaledLong = (long) (unscaled + (v < 0 ? -0.5 : 0.5)); 
    return (double) unscaledLong / TENS[precision]; 
}

примечание: вы можете использовать BigDecimal для выполнения окончательного округления. особенно если вам нужен специальный метод раунда.

1 голос
/ 06 октября 2010

Ну, у вас не так много вариантов в реальности:

Вы можете реорганизовать проект, чтобы использовать, например, BigDecimal (или что-то, что лучше соответствует его потребностям) для представления денег.

Будьте предельно осторожны при переполнении / недополнении и потере точности, что означает добавление множества проверок и ненужный рефакторинг еще большей части системы.Не говоря уже о том, сколько исследований потребуется для этого.

Держите вещи такими, какие они есть, и надеюсь, что никто не заметит (это шутка).

ИМХО, лучшее решениебыло бы просто рефакторинг это.Это может быть какой-то тяжелый рефакторинг, но зло уже совершено, и я считаю, что это должен быть ваш лучший вариант.

Best, Vassil

PS Да, и вы можете рассматривать деньги как целые числа (считаяцентов), но это не очень хорошая идея, если у вас будут конвертации валют, начисление процентов и т. д.

0 голосов
/ 06 октября 2010

Я думаю, что эта ситуация, по крайней мере, минимально возможна для вашего кода. Вы получаете значение как двойное через структуру ORM. Затем вы можете преобразовать его в BigDecimal с помощью статического метода valueOf (см. здесь , почему) перед выполнением каких-либо математических / вычислений для него, а затем преобразовать его обратно в удвоенное значение только для хранения.

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

Это может не охватывать 100% случаев (я бы особенно беспокоился о том, что делает драйвер ORM или JDBC для преобразования типа double в тип Number), но это намного лучше, чем просто выполнять математические вычисления для сырье удваивается.

Однако я далеко не убежден, что этот подход на самом деле дешевле для компании в долгосрочной перспективе.

...