Подтвердить что ты не робот

Использование BigDecimal для работы с валютами

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

4b9b3361

Ответ 1

Вот несколько советов:

  • Используйте BigDecimal для вычислений, если вам нужна точность, которую он предлагает (значения денег часто нуждаются в этом).
  • Используйте класс NumberFormat для отображения. Этот класс позаботится о проблемах локализации для сумм в разных валютах. Однако он будет принимать только примитивы; поэтому, если вы можете принять небольшое изменение в точности из-за преобразования в double, вы можете использовать этот класс.
  • При использовании класса 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()) );

Ответ 2

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

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);

Ответ 3

Или, ждите JSR-354. Java Money and Currency API в ближайшее время!

Ответ 4

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

Конструкция этого класса Money призвана сделать выражения более естественными. Например:

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

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

Ответ 8

NumberFormat.getNumberInstance(java.util.Locale.US).format(num);

Ответ 9

Я был бы радикальным. Нет 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 в виде String в Swagger.

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

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

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


Обновить

fooobar.com/info/9563495/...

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

Это пример второго подхода. Используйте масштабирование. http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigdecimals-with-morphia-for-mongodb.html