Использование BigDecimal для работы с валютами - PullRequest
63 голосов
/ 01 сентября 2009

Я пытался создать свой собственный класс для валют, использующих длинные, но, очевидно, я должен использовать вместо него BigDecimal. Может ли кто-нибудь помочь мне начать? Как лучше всего использовать BigDecimal с для долларовых валют, например, сделать это как минимум, но не более 2 десятичных знаков для центов и т. Д. API для BigDecimal огромен, и я не знаю, какой методы для использования. Кроме того, BigDecimal имеет лучшую точность, но не все ли потеряно, если оно проходит через double? если я сделаю новый BigDecimal(24.99), чем он будет отличаться от использования double? Или я должен использовать конструктор, который использует String вместо?

Ответы [ 9 ]

73 голосов
/ 01 сентября 2009

Вот несколько подсказок:

  1. Используйте BigDecimal для вычислений, если вам нужна точность, которую он предлагает (это часто требуется для денежных значений).
  2. Используйте класс NumberFormat для отображения. Этот класс будет заниматься вопросами локализации для сумм в разных валютах. Тем не менее, он будет принимать только примитивы; поэтому, если вы можете принять небольшое изменение в точности из-за преобразования в double, вы можете использовать этот класс.
  3. При использовании класса NumberFormat используйте метод scale() в экземпляре BigDecimal, чтобы установить точность и метод округления.

PS: Если вам интересно, BigDecimal всегда лучше, чем double, когда вам нужно представлять денежные значения в Java .

PPS:

Создание BigDecimal экземпляров

Это довольно просто, поскольку BigDecimal предоставляет конструкторам возможность принимать примитивные значения и String объектов. Вы можете использовать их, предпочтительно тот, который берет String объект . Например,

BigDecimal modelVal = new BigDecimal("24.455");
BigDecimal displayVal = modelVal.setScale(2, RoundingMode.HALF_EVEN);

Отображение BigDecimal экземпляров

Вы можете использовать вызовы методов setMinimumFractionDigits и setMaximumFractionDigits, чтобы ограничить объем отображаемых данных.

NumberFormat usdCostFormat = NumberFormat.getCurrencyInstance(Locale.US);
usdCostFormat.setMinimumFractionDigits( 1 );
usdCostFormat.setMaximumFractionDigits( 2 );
System.out.println( usdCostFormat.format(displayVal.doubleValue()) );
34 голосов
/ 15 марта 2010

Я бы порекомендовал немного исследований по шаблону денег. Мартин Фаулер в своей книге «Анализ шаблона» раскрыл это более подробно.

public class Money {

    private static final Currency USD = Currency.getInstance("USD");
    private static final RoundingMode DEFAULT_ROUNDING = RoundingMode.HALF_EVEN;

    private final BigDecimal amount;
    private final Currency currency;   

    public static Money dollars(BigDecimal amount) {
        return new Money(amount, USD);
    }

    Money(BigDecimal amount, Currency currency) {
        this(amount, currency, DEFAULT_ROUNDING);
    }

    Money(BigDecimal amount, Currency currency, RoundingMode rounding) {
        this.currency = currency;      
        this.amount = amount.setScale(currency.getDefaultFractionDigits(), rounding);
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public Currency getCurrency() {
        return currency;
    }

    @Override
    public String toString() {
        return getCurrency().getSymbol() + " " + getAmount();
    }

    public String toString(Locale locale) {
        return getCurrency().getSymbol(locale) + " " + getAmount();
    }   
}

Приходят к использованию:

Вы бы представляли все деньги, используя объект Money, а не BigDecimal. Представление денег как большого десятичного знака будет означать, что у вас будет возможность форматировать деньги везде, где вы их отображаете. Только представьте, изменится ли стандарт дисплея. Вам придется вносить изменения повсюду. Вместо этого, используя шаблон Money, вы централизуете форматирование денег в одном месте.

Money price = Money.dollars(38.28);
System.out.println(price);
7 голосов
/ 18 апреля 2013

Или дождитесь JSR-354 . Скоро появится API Java Money and Currency!

1 голос
/ 01 марта 2016

Примитивные числовые типы полезны для хранения отдельных значений в памяти. Но при вычислениях с использованием типов double и float возникают проблемы с округлением. Это происходит из-за того, что представление в памяти не отображается точно в значение. Например, двойное значение должно занимать 64 бита, но Java не использует все 64 бита. Оно хранит только то, что считает важными частями числа. Таким образом, вы можете получить неправильные значения при добавлении значений типа float или double.

Смотрите короткий клип https://youtu.be/EXxUSz9x7BM

1 голос
/ 01 сентября 2009

1) Если вы ограничены точностью double, одной из причин использования BigDecimal s является выполнение операций с BigDecimal s, созданными из double s.

2) BigDecimal состоит из целочисленного немасштабированного значения произвольной точности и неотрицательной 32-битной целочисленной шкалы, в то время как double оборачивает в объект значение примитивного типа double. Объект типа Double содержит одно поле с типом double

3) Это не должно иметь никакого значения

У вас не должно быть трудностей с $ и точностью. Один из способов сделать это - использовать System.out.printf

0 голосов
/ 14 февраля 2019

Я был бы радикальным. Нет BigDecimal.

Вот отличная статья https://lemnik.wordpress.com/2011/03/25/bigdecimal-and-your-money/

Идеи отсюда.

import java.math.BigDecimal;

public class Main {

    public static void main(String[] args) {
        testConstructors();
        testEqualsAndCompare();
        testArithmetic();
    }

    private static void testEqualsAndCompare() {
        final BigDecimal zero = new BigDecimal("0.0");
        final BigDecimal zerozero = new BigDecimal("0.00");

        boolean zerosAreEqual = zero.equals(zerozero);
        boolean zerosAreEqual2 = zerozero.equals(zero);

        System.out.println("zerosAreEqual: " + zerosAreEqual + " " + zerosAreEqual2);

        int zerosCompare = zero.compareTo(zerozero);
        int zerosCompare2 = zerozero.compareTo(zero);
        System.out.println("zerosCompare: " + zerosCompare + " " + zerosCompare2);
    }

    private static void testArithmetic() {
        try {
            BigDecimal value = new BigDecimal(1);
            value = value.divide(new BigDecimal(3));
            System.out.println(value);
        } catch (ArithmeticException e) {
            System.out.println("Failed to devide. " + e.getMessage());
        }
    }

    private static void testConstructors() {
        double doubleValue = 35.7;
        BigDecimal fromDouble = new BigDecimal(doubleValue);
        BigDecimal fromString = new BigDecimal("35.7");

        boolean decimalsEqual = fromDouble.equals(fromString);
        boolean decimalsEqual2 = fromString.equals(fromDouble);

        System.out.println("From double: " + fromDouble);
        System.out.println("decimalsEqual: " + decimalsEqual + " " + decimalsEqual2);
    }
}

Он печатает

From double: 35.7000000000000028421709430404007434844970703125
decimalsEqual: false false
zerosAreEqual: false false
zerosCompare: 0 0
Failed to devide. Non-terminating decimal expansion; no exact representable decimal result.

Как насчет хранения BigDecimal в базе данных? Черт, это также хранит как двойное значение ??? По крайней мере, если я использую mongoDb без какой-либо расширенной конфигурации, он будет хранить BigDecimal.TEN как 1E1.

Возможные решения?

Я пришел с одним - используйте String для хранения BigDecimal в Java как String в базе данных. У вас есть проверка, например @NotNull, @Min(10) и т. Д. Затем вы можете использовать триггер при обновлении или сохранении, чтобы проверить, является ли текущая строка нужным вам числом. Там нет триггеров для монго, хотя. Есть ли встроенный способ вызова функций запуска Mongodb?

Есть один недостаток, который мне доставляет удовольствие - BigDecimal as String в определении Swagger

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

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

Стандартное длинное значение может хранить текущую стоимость государственного долга США (в центах, а не в долларах) 6477 раз без каких-либо переполнений. Более того: это целочисленный тип, а не с плавающей точкой. Это облегчает и точнее работать, а также гарантирует поведение.


Обновление

https://stackoverflow.com/a/27978223/4587961

Возможно, в будущем MongoDb добавит поддержку BigDecimal. https://jira.mongodb.org/browse/SERVER-1393 3.3.8, кажется, сделал это.

Это пример второго подхода. Используйте масштабирование. http://www.technology -ebay.de / The-команды / мобильный-де / блог / картирование-bigdecimals-с морфием-за-mongodb.html

0 голосов
/ 02 августа 2016
NumberFormat.getNumberInstance(java.util.Locale.US).format(num);
0 голосов
/ 01 сентября 2009

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

Дизайн этого класса Money предназначен для того, чтобы сделать выражения более естественными. Например:

if ( amount.lt(hundred) ) {
 cost = amount.times(price); 
}

Инструмент WEB4J имеет аналогичный класс, называемый Decimal, который немного лучше, чем класс Money.

0 голосов
/ 01 сентября 2009

Используйте BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP), если хотите округлить до 2 десятичных знаков для центов. Имейте в виду ошибку округления при выполнении вычислений. Вы должны быть последовательны, когда вы будете делать округление денежной стоимости. Либо выполните округление в конце только один раз после выполнения всех вычислений, либо примените округление к каждому значению перед выполнением любых вычислений. Какой из них использовать, будет зависеть от требований вашего бизнеса, но в целом я думаю, что выполнение округления в самом конце, кажется, лучше для меня.

Используйте String при построении BigDecimal для денежной оценки. Если вы используете double, он будет иметь конечные значения с плавающей запятой в конце. Это связано с архитектурой компьютера относительно того, как значения double / float представлены в двоичном формате.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...