Javaアプリケーションでお金を使うのに最適なデータ型は何ですか?
Javaには、ISO 4217通貨コードを表す Currency
クラスがあります。 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") );
}
}
可能な最小値を表す整数型。つまり、プログラムはドル/ユーロではなくセントで考える必要があります。
これは、GUIがドル/ユーロに変換することを妨げるものではありません。
BigDecimal を使用できます。FloatまたはDoubleを使用しない理由の説明はこちらにあります。 通貨を表すためにDoubleまたはFloatを使用しない理由
JSR 354:Money and Currency API
JSR 354は、MoneyとCurrencyで包括的な計算を表現、転送、および実行するためのAPIを提供します。次のリンクからダウンロードできます。
JSR 354:Money and Currency APIダウンロード
仕様は次のもので構成されています:
- Eを処理するためのAPI。 g。金額と通貨
- 交換可能な実装をサポートするAPI
- 実装クラスのインスタンスを作成するためのファクトリー
- 金額の計算、変換、フォーマットの機能
- MoneyおよびCurrencyを操作するためのJava API。これはJava 9.に含まれる予定です。
- すべての仕様クラスとインターフェースはjavax.money。*パッケージにあります。
JSR 354のサンプル例:Money and Currency 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のコレクションを使用する場合、フィルタリング、ソート、およびグループ化のためのいくつかのNiceユーティリティメソッドが利用可能です。
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を調べる9 Money and Currency API(JSR 354)
関連項目: JSR 354-通貨とお金
Joda Money を使用します
バージョン0.6のままですが、非常に有望に見えます
マイクロベンチマーク(JMH)を実行して、パフォーマンスに関してMoneta(Java通貨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を使用して金銭的価値を表す必要があります。さまざまな丸めモードを使用でき、金融アプリケーションでは、丸めモードは、多くの場合、法律で義務付けられている場合もある厳しい要件です。
単純なケース(1通貨)の場合は、Integer
/Long
で十分です。お金をセント(...)または100分の1セントまたは1000分の1セント(固定分周器で必要な精度)に保ちます
BigDecimalは、通貨に使用するのに最適なデータ型です。
通貨用のコンテナは非常に多くありますが、それらはすべてBigDecimalを基になるデータ型として使用します。おそらくBigDecimal.ROUND_HALF_EVEN丸めを使用して、BigDecimalを間違えることはありません。
Tiny Types を使用するのが好きです。これは、以前の回答が示唆したように、double、BigDecimal、またはintをラップします。 (精度の問題が発生しない限り、doubleを使用します)。
タイニータイプはタイプセーフティを提供するので、ダブルマネーを他のダブルと混同しないようにします。