Ответ 1
Java имеет Currency
класс, который представляет коды кодов ISO 4217.
BigDecimal
- лучший тип для представления десятичных значений валюты.
Joda Money предоставил библиотеку для представления денег.
Какой тип данных вы должны использовать для денег в Java?
Java имеет Currency
класс, который представляет коды кодов ISO 4217.
BigDecimal
- лучший тип для представления десятичных значений валюты.
Joda Money предоставил библиотеку для представления денег.
Вы можете использовать Money and Currency API (JSR 354). Вы можете использовать этот API при условии, что вы добавите соответствующие зависимости в ваш проект.
Для Java 8 добавьте следующую ссылочную реализацию в качестве зависимости к вашему pom.xml
:
<dependency>
<groupId>org.javamoney</groupId>
<artifactId>moneta</artifactId>
<version>1.0</version>
</dependency>
Эта зависимость будет транзитивно добавлять javax.money:money-api
в качестве зависимости.
Затем вы можете использовать API:
package com.example.money;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.is;
import java.util.Locale;
import javax.money.Monetary;
import javax.money.MonetaryAmount;
import javax.money.MonetaryRounding;
import javax.money.format.MonetaryAmountFormat;
import javax.money.format.MonetaryFormats;
import org.junit.Test;
public class MoneyTest {
@Test
public void testMoneyApi() {
MonetaryAmount eurAmount1 = Monetary.getDefaultAmountFactory().setNumber(1.1111).setCurrency("EUR").create();
MonetaryAmount eurAmount2 = Monetary.getDefaultAmountFactory().setNumber(1.1141).setCurrency("EUR").create();
MonetaryAmount eurAmount3 = eurAmount1.add(eurAmount2);
assertThat(eurAmount3.toString(), is("EUR 2.2252"));
MonetaryRounding defaultRounding = Monetary.getDefaultRounding();
MonetaryAmount eurAmount4 = eurAmount3.with(defaultRounding);
assertThat(eurAmount4.toString(), is("EUR 2.23"));
MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat(Locale.GERMAN);
assertThat(germanFormat.format(eurAmount4), is("EUR 2,23") );
}
}
Интегральный тип, представляющий наименьшее возможное значение. Другими словами, ваша программа должна думать в центах не в долларах/евро.
Это не должно помешать вам перевести его обратно в доллары/евро.
Можно использовать BigDecimal, хорошее объяснение того, почему не использовать Float или Double, можно увидеть здесь: почему бы не использовать Double или Float для представления валюты?
JSR 354: API денег и валют
JSR 354 предоставляет API для представления, транспортировки и выполнения комплексных расчетов с помощью Money and Currency. Вы можете скачать его по этой ссылке:
JSR 354: Загрузка денег и валюты API
Спецификация состоит из следующих вещей:
- API для обработки e. г. денежные суммы и валюты.
- API для поддержки взаимозаменяемых реализаций
- Заводы для создания экземпляров классов реализации
- Функциональность для расчетов, преобразования и форматирования денежных сумм
- API Java для работы с деньгами и валютами, который планируется включить в Java 9.
- Все классы спецификации и интерфейсы расположены в пакете javax.money. *.
Примеры примеров JSR 354: API денег и валют:
Пример создания MonetaryAmount и его печати на консоли выглядит так:
MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
При использовании ссылочного API реализации необходимый код намного проще:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
API также поддерживает вычисления с помощью MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit и MonetaryAmount
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount имеет различные методы, которые позволяют получить доступ к присвоенной валюте, числовой величине, ее точности и т.д.
MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();
int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5
// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;
MonetaryAmounts можно округлить с помощью оператора округления:
CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35
При работе с коллекциями MonetaryAmounts доступны некоторые полезные утилиты для фильтрации, сортировки и группировки.
List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));
Пользовательские операции MonetaryAmount
// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
return Money.of(tenPercent, amount.getCurrency());
};
MonetaryAmount dollars = Money.of(12.34567, "USD");
// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567
Ресурсы:
Обработка денег и валют на Java с помощью JSR 354
Вглядываясь в Java 9 Money and Currency API (JSR 354)
Смотрите также: JSR 354 - Валюта и деньги
Я бы использовал Joda Money
Он все еще находится в версии 0.6, но выглядит очень перспективным
Я сделал микропредметку (JMH) для сравнения Moneta (java currency JSR 354) с BigDecimal с точки зрения производительности.
Удивительно, что производительность BigDecimal кажется лучше, чем у moneta. Я использовал следующую конфигурацию moneta:
org.javamoney.moneta.Money.defaults.precision = 19 org.javamoney.moneta.Money.defaults.roundingMode = HALF_UP
package com.despegar.bookedia.money;
import org.javamoney.moneta.FastMoney;
import org.javamoney.moneta.Money;
import org.openjdk.jmh.annotations.*;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.concurrent.TimeUnit;
@Measurement(batchSize = 5000, iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 2)
@Threads(value = 1)
@Fork(value = 1)
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
public class BigDecimalBenchmark {
private static final Money MONEY_BASE = Money.of(1234567.3444, "EUR");
private static final Money MONEY_SUBSTRACT = Money.of(232323, "EUR");
private static final FastMoney FAST_MONEY_SUBSTRACT = FastMoney.of(232323, "EUR");
private static final FastMoney FAST_MONEY_BASE = FastMoney.of(1234567.3444, "EUR");
MathContext mc = new MathContext(10, RoundingMode.HALF_UP);
@Benchmark
public void bigdecimal_string() {
new BigDecimal("1234567.3444").subtract(new BigDecimal("232323")).multiply(new BigDecimal("3.4"), mc).divide(new BigDecimal("5.456"), mc);
}
@Benchmark
public void bigdecimal_valueOf() {
BigDecimal.valueOf(12345673444L, 4).subtract(BigDecimal.valueOf(232323L)).multiply(BigDecimal.valueOf(34, 1), mc).divide(BigDecimal.valueOf(5456, 3), mc);
}
@Benchmark
public void fastmoney() {
FastMoney.of(1234567.3444, "EUR").subtract(FastMoney.of(232323, "EUR")).multiply(3.4).divide(5.456);
}
@Benchmark
public void money() {
Money.of(1234567.3444, "EUR").subtract(Money.of(232323, "EUR")).multiply(3.4).divide(5.456);
}
@Benchmark
public void money_static(){
MONEY_BASE.subtract(MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
}
@Benchmark
public void fastmoney_static() {
FAST_MONEY_BASE.subtract(FAST_MONEY_SUBSTRACT).multiply(3.4).divide(5.456);
}
}
Результат
Benchmark Mode Cnt Score Error Units
BigDecimalBenchmark.bigdecimal_string thrpt 10 479.465 ± 26.821 ops/s
BigDecimalBenchmark.bigdecimal_valueOf thrpt 10 1066.754 ± 40.997 ops/s
BigDecimalBenchmark.fastmoney thrpt 10 83.917 ± 4.612 ops/s
BigDecimalBenchmark.fastmoney_static thrpt 10 504.676 ± 21.642 ops/s
BigDecimalBenchmark.money thrpt 10 59.897 ± 3.061 ops/s
BigDecimalBenchmark.money_static thrpt 10 184.767 ± 7.017 ops/s
Пожалуйста, не стесняйтесь исправлять меня, если я чего-то не хватает
Для представления денежных значений вы должны использовать BigDecimal. Он позволяет использовать различные режимы округления и в финансовые приложения, режим округления часто является жестким требованием которые могут быть санкционированы законом.
Для простого случая (одна валюта) достаточно Integer
/Long
.
Держите деньги в центах (...) или сотых/тысячных центов (любая точность, которая вам нужна с фиксированным делителем)
BigDecimal - лучший тип данных для использования в валюте.
Существует множество контейнеров для валюты, но все они используют BigDecimal в качестве базового типа данных. Вы не ошибетесь в BigDecimal, возможно, используя округление BigDecimal.ROUND_HALF_EVEN.
Мне нравится использовать Tiny Types, который бы обернул либо двойной, BigDecimal, либо int, как предполагали предыдущие ответы. (Я бы использовал двойной, если не возникли проблемы с точностью).
A Tiny Type дает вам безопасность типа, поэтому вы не путаете двойные деньги с другими двойниками.