web-dev-qa-db-ja.com

BigDecimalを使用して通貨を操作する

Longを使用して通貨の独自のクラスを作成しようとしましたが、代わりにBigDecimalを使用する必要があるようです。誰かが私を始めるのを手伝ってくれますか?ドル通貨にBigDecimalsを使用する最良の方法は、少なくともセントの小数点以下2桁以下にするなどです。BigDecimalのAPIは巨大です。使用する方法がわかりません。また、BigDecimalの精度は向上していますが、doubleを通過した場合、すべてが失われるわけではありませんか?新しいBigDecimal(24.99)を実行すると、doubleを使用した場合とどう違うのですか?または、代わりにStringを使用するコンストラクターを使用する必要がありますか?

64
mk12

以下にいくつかのヒントを示します。

  1. 提供する精度が必要な場合は、計算にBigDecimalを使用します(お金の値はしばしばこれを必要とします)。
  2. NumberFormat クラスを使用して表示します。このクラスは、異なる通貨の金額のローカライズの問題を処理します。ただし、プリミティブのみを取ります。したがって、doubleへの変換による精度のわずかな変化を受け入れることができる場合、このクラスを使用できます。
  3. NumberFormatクラスを使用する場合、BigDecimalインスタンスでscale()メソッドを使用して、精度と丸め方法を設定します。

PS:あなたが疑問に思っている場合、 BigDecimalは、Javaで金額を表す必要がある場合、doubleよりも常に優れています

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()) );
73
Vineet Reynolds

Money Patternについて少し研究することをお勧めします。 Martin Fowlerは著書のAnalysis patternでこれをより詳細に説明しています。

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

または、 JSR-354 を待ちます。 Java Money and Currency APIは近日公開予定!

7
Victor Grazi

1)double精度に制限されている場合、BigDecimalsを使用する理由の1つは、BigDecimalsから作成されたdoublesを使用した操作を実現することです。

2)BigDecimalは、任意精度の整数スケールなし値と非負の32ビット整数スケールで構成され、doubleはプリミティブ型doubleの値をオブジェクトにラップします。タイプDoubleのオブジェクトには、タイプがdoubleである単一のフィールドが含まれます

3)違いはありません

$と精度に問題はないはずです。それを行う1つの方法は、System.out.printf

1
Diego Dias

セントを小数点以下2桁に切り上げる場合は、BigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)を使用します。ただし、計算を行うときは、四捨五入のエラーに注意してください。お金の価値の丸めを行うときは、一貫性が必要です。すべての計算が行われた後、最後に一度だけ丸めを行うか、計算を行う前に各値に丸めを適用します。どちらを使用するかは、ビジネス要件によって異なりますが、一般的に、最後に丸めを行うことは私にとって理にかなっていると思います。

お金の価値のためにStringを構築するとき、BigDecimalを使用します。 doubleを使用すると、末尾に浮動小数点値が末尾に付きます。これは、double/float値がバイナリ形式でどのように表されるかに関するコンピューターアーキテクチャによるものです。

1
tim_wonil

プリミティブ数値型は、単一の値をメモリに保存するのに役立ちます。ただし、double型とfloat型を使用した計算を扱う場合、丸めに問題があります。メモリ表現が値に正確にマッピングされないために発生します。たとえば、double値は64ビットを取ると想定されていますが、Javaは64ビットすべてを使用するわけではありません。 float型またはdouble型の値を一緒に追加すると、間違った値になります。

短いクリップをご覧ください https://youtu.be/EXxUSz9x7BM

1
Gregory Nozik

Javapractices.comでこれを行う方法の広範な例があります。特に Money クラスを参照してください。これは、BigDecimalを直接使用するよりも金銭的な計算を簡単にするためのものです。

このMoneyクラスの設計は、式をより自然にすることを目的としています。例えば:

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

WEB4Jツールには Decimal と呼ばれる同様のクラスがあり、Moneyクラスよりも少し洗練されています。

0
John O
NumberFormat.getNumberInstance(Java.util.Locale.US).format(num);
0
Heisenberg

私は過激になります。 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トリガー関数呼び出しの組み込み方法はありますか?

私が楽しんでいる欠点が1つあります- Swagger defenitionの文字列としてのBigDecimal

Swaggerを生成する必要があるため、フロントエンドチームは、文字列として表示される数字を渡すことを理解しています。 DateTimeたとえば、文字列として表示されます。

上記の記事で読んだ別のクールなソリューションがあります...longを使用して正確な数値を保存します。

標準のlong値は、米国国債の現在の値(ドルではなくセント)を6477回オーバーフローなしで保存できます。さらに、浮動小数点ではなく整数型です。これにより、作業が簡単かつ正確になり、動作が保証されます。


更新

https://stackoverflow.com/a/27978223/4587961

将来的には、MongoDbがBigDecimalのサポートを追加するかもしれません。 https://jira.mongodb.org/browse/SERVER-139 3.3.8はこれを完了したようです。

これは、2番目のアプローチの例です。スケーリングを使用します。 http://www.technology-ebay.de/the-teams/mobile-de/blog/mapping-bigdecimals-with-morphia-for-mongodb.html

0
Yan Khonski