Использование BigDecimal для работы с валютами
Я пытался создать свой собственный класс для валют, использующих длинные, но, видимо, я должен использовать BigDecimal
вместо. Может ли кто-нибудь помочь мне начать? Какой будет лучший способ использовать BigDecimal
s для долларовых валют, например, сделать это как минимум, но не более 2 десятичных знаков для центов и т. д. API для BigDecimal
огромен, и я не знаю, какие методы использовать. Также, BigDecimal
имеет лучшую точность, но разве не все потеряно, если оно проходит через double
? если я сделаю новый BigDecimal(24.99)
как это будет отличаться от использования double
? Или я должен использовать конструктор, который использует String
вместо?
9 ответов
Вот несколько советов:
- использование
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()) );
Я бы порекомендовал немного исследований по шаблону денег. Мартин Фаулер в своей книге "Анализ паттернов" рассказал об этом более подробно.
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);
Или дождитесь JSR-354. Скоро появится API Java Money and Currency!
Использование BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)
когда вы хотите округлить до 2 десятичных знаков для центов. Имейте в виду ошибку округления при выполнении вычислений. Вы должны быть последовательны, когда вы будете делать округление денежной стоимости. Либо выполните округление в конце только один раз после выполнения всех вычислений, либо примените округление к каждому значению перед выполнением любых вычислений. Какой из них использовать, будет зависеть от требований вашего бизнеса, но в целом, я думаю, что округление в самом конце, кажется, лучше для меня.
Использовать String
когда вы строите BigDecimal
за денежную стоимость. Если вы используете double
, он будет иметь конечные значения с плавающей точкой в конце. Это связано с компьютерной архитектурой относительно того, как double
/float
значения представлены в двоичном формате.
Примитивные числовые типы полезны для хранения отдельных значений в памяти. Но при вычислениях с использованием типов double и float возникают проблемы с округлением. Это происходит из-за того, что представление в памяти не отображается точно в значение. Например, двойное значение должно занимать 64 бита, но Java не использует все 64 бита. Оно хранит только то, что считает важными частями числа. Таким образом, вы можете получить неправильные значения при добавлении значений типа float или double.
Пожалуйста, посмотрите короткий клип https://youtu.be/EXxUSz9x7BM
1) Если вы ограничены double
точность, одна из причин использования BigDecimal
s, чтобы реализовать операции с BigDecimal
S создан из double
s.
2) BigDecimal
состоит из целочисленного немасштабируемого значения произвольной точности и неотрицательной 32-битной целочисленной шкалы, в то время как значение типа double содержит значение примитивного типа double
в объекте. Объект типа Double
содержит одно поле, тип которого double
3) Это не должно иметь никакого значения
У вас не должно быть проблем с $ и точностью. Один из способов сделать это - использовать System.out.printf
Я был бы радикальным. Нет 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 раз без каких-либо переполнений. Более того: это целочисленный тип, а не с плавающей точкой. Это облегчает и точнее работать, а также гарантирует поведение.
Обновить
/questions/12563944/magazin-liftov-bigdecimal-v-mongodb/12563954#12563954
Возможно, в будущем 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
Существует обширный пример того, как это сделать на javapractices.com. Смотрите, в частности, Money
класс, который предназначен для упрощения денежных расчетов, чем использование BigDecimal
непосредственно.
Дизайн этого Money
Класс предназначен для того, чтобы сделать выражения более естественными. Например:
if ( amount.lt(hundred) ) {
cost = amount.times(price);
}
Инструмент WEB4J имеет аналогичный класс, называемый Decimal
, который немного более отполирован, чем Money
учебный класс.
NumberFormat.getNumberInstance(java.util.Locale.US).format(num);