BigDecimal を使用して通貨を操作する
-
20-09-2019 - |
質問
ロングを使用して通貨用の独自のクラスを作成しようとしていましたが、どうやら使用する必要があるようです BigDecimal
その代わり。誰かが私が始めるのを手伝ってくれませんか?一番良い使い方は何でしょうか BigDecimal
ドル通貨の場合、セントの小数点以下 2 桁以下にするなど。の API BigDecimal
膨大な量なので、どの方法を使用すればよいかわかりません。また、 BigDecimal
の方が精度は良いですが、それを通過するとすべてが失われるのではありませんか? double
?新しいことをしたら BigDecimal(24.99)
, を使うのとどう違うのか? double
?それとも、を使用するコンストラクターを使用する必要がありますか? String
その代わり?
解決
以下にいくつかのヒントを示します。
- 使用
BigDecimal
提供される精度が必要な場合は、計算に使用します (金銭の値は多くの場合これを必要とします)。 - 使用
NumberFormat
表示用のクラス。このクラスは、さまざまな通貨での金額のローカリゼーションの問題を処理します。ただし、プリミティブのみを受け取ります。したがって、への変換による精度のわずかな変化を許容できる場合は、double
, 、このクラスを使用できます。 - を使用するときは、
NumberFormat
クラスの場合は、scale()
のメソッドBigDecimal
インスタンスを使用して精度と丸め方法を設定します。
追伸:疑問に思われた方のために、 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()) );
他のヒント
私はお金のパターンに少し研究をお勧めします。 Martin Fowler氏は、彼の著書の分析パターンでより詳細にこれをカバーしています。
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 のを待ちます。 Javaのマネーと通貨APIはもうすぐ!
1)あなたはdouble
精度に限定されている場合は、BigDecimal
sを使用する1つの理由は、BigDecimal
sから作成double
sとの動作を実現することです。
BigDecimal
の値をラップしながら、 2)double
は、任意精度の整数スケールなしの値と非負32ビット整数スケールで構成されています。型Double
のオブジェクトは、そのタイプdouble
れる単一のフィールドが含まれて
3)それは作るべき違いはありません。
あなたは$と精度で支障があってはいけません。それを行うための1つの方法は、
System.out.printf
を使用しています BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)
を使用してください。あなたがが計算を行う際に、エラーを丸めるのに注意してください。あなたはお金の価値の丸めを行うことになる際に一貫している必要があります。どちらのは一度だけ、すべての計算が行われた後、最後に丸め権利を行う、または任意の計算を行う前に、各値に丸め適用されます。どちらを使用するためには、ビジネス要件に依存するが、一般的に、私は最後に丸め、右をやって考える私にはより良い感覚を作るようです。
あなたはお金の価値のためにString
を構築する際BigDecimal
を使用してください。あなたがdouble
を使用する場合は、末尾に浮動小数点値を持つことになります。これはdouble
/ float
値はバイナリ形式で表現される方法に関するコンピュータ・アーキテクチャに起因するものである。
プリミティブ数値型は、メモリ内の単一の値を格納するために有用です。メモリ表現が値に正確にマップされませんので、しかし、ダブル、フロートタイプを使用して計算を扱うとき、rounding.Itに問題があると起こります。例えば、二重の値は、64ビットを取ることになっているが、Javaは、それは数の重要な部分をどう思うか、すべて64 bits.It店舗のみを使用していません。だから、あなたがfloatまたはdouble型の一緒に値を追加するとき、間違った値に到着することができます。
短いクリップをご覧ください。 https://youtu.be/EXxUSz9x7BMする
javapractices.comにこれを行う方法の豊富な例があります。特に金銭計算を行うことを意図している Money
のクラスを、参照してください。直接BigDecimal
を使用してよりも簡単。
このMoney
クラスのデザインは、表現がより自然にすることを目的としています。たとえばます:
if ( amount.lt(hundred) ) {
cost = amount.times(price);
}
WEB4Jツールは Decimal
<呼ばれ、同様のクラスを持っています/ A>、Money
クラスよりも少し研磨されます。
NumberFormat.getNumberInstance(java.util.Locale.US).format(num);
私なら過激になるでしょう。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
.
可能な解決策?
私は 1 つを提供しました - String を使用して BigDecimal を Java に保存します 弦 データベースに。たとえば、検証があります @NotNull
, @Min(10)
, 、など...その後、更新または保存時にトリガーを使用して、現在の文字列が必要な数値であるかどうかを確認できます。ただし、mongo にはトリガーはありません。Mongodb トリガー関数呼び出しの組み込み方法はありますか?
私が楽しんでいる欠点が 1 つあります - Swagger 定義の文字列としての BigDecimal
フロントエンド チームが String として提示された数値を渡すことを理解できるように、Swagger を生成する必要があります。 日付時刻 たとえば、文字列として表現されます。
上記の記事で読んだ別の素晴らしい解決策があります...使用 長さ 正確な数値を保存します。
標準の Long 値では、オーバーフローすることなく、米国国債の現在価値 (ドルではなくセントとして) を 6,477 回保存できます。そのうえ:浮動小数点ではなく整数型です。これにより、操作が簡単かつ正確になり、動作が保証されます。
アップデート
https://stackoverflow.com/a/27978223/4587961
おそらく将来、MongoDb は BigDecimal のサポートを追加するでしょう。https://jira.mongodb.org/browse/SERVER-13933.3.8 ではこれが行われているようです。
2番目のアプローチの例です。スケーリングを使用します。http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigDecimals-with-morphia-for-mongodb.html