通貨を表すためにDoubleまたはFloatを使用しないのはなぜですか?


939

私は常にお金やタイプを表現しないように言われてきました、そして今度はあなたに質問をします:なぜですか?doublefloat

私にはそれが何であるかがわからない、非常に正当な理由があると確信しています。


4
このSOの質問を参照してください:丸めエラー?
ジェフ緒方

80
明確にするために、通貨だけでなく、正確さが要求される用途には使用しないでください。
Jeff

152
正確性が要求される用途には使用しないでください。しかし、doubleの53の有効ビット(10進数で約16桁)は、通常、精度が必要なだけの場合には十分です。
dan04

21
@jeffあなたのコメントは、バイナリ浮動小数点が何に適しているのか、何が適切でないのかを完全に誤って伝えています。以下のzneakによる回答を読んで、誤解を招くコメントを削除してください。
Pascal Cuoq 2014

回答:


985

floatとdoubleは、お金に使用する10の倍数を正確に表すことができないためです。この問題は、Javaだけでなく、base 2浮動小数点型を使用するプログラミング言語でも発生します。

基数10では、10.25を1025 * 10 -2(10の累乗の整数倍)と書くことができます。IEEE-754浮動小数点数は異なりますが、それらを考える非常に簡単な方法は、代わりに2の累乗を掛けることです。たとえば、164 * 2 -4(2 の累乗の整数倍)を見ると、これも10.25に等しくなります。これは、メモリ内での数値の表現方法ではありませんが、数学の意味は同じです。

基数10でも、この表記はほとんどの単純な分数を正確に表すことができません。たとえば、1/3を表すことはできません。10進表記は繰り返されているため(0.3333 ...)、10の累乗で乗算して1/3を得ることができる有限の整数はありません。3の長いシーケンスと333333333 * 10 -10のような小さな指数で解決できますが、正確ではありません。3を掛けると、1にはなりません。

ただし、お金を数える目的で、少なくとも米ドルが1ドルの桁で評価される国の場合、通常必要なのは10 -2の倍数を格納できることだけなので、実際には問題になりません。その1/3は表現できません。

floatとdoubleの問題は、お金のような数値の大部分が整数の2の累乗の正確な表現を持たないことです。実際、0と1の間の0.01の倍数のみ(これは、 IEEE 754の2進浮動小数点数として正確に表すことができる整数セントであるため、お金で)は0、0.25、0.5、0.75、1です。その他はすべて少しずれています。0.333333の例に例えると、0.1の浮動小数点値を取得して10倍すると、1は得られません。

ソフトウェアが小さなエラーを四捨五入するので、お金をdoubleまたfloatはとして表すとおそらく最初は見栄えがしますが、不正確な数値に対してさらに加算、減算、乗算、および除算を実行すると、エラーが複雑になり、目に見える値になります正確ではない。これは、浮動小数点数と倍精度浮動小数点数を、基本10の累乗の倍数の完全な精度が必要とされるお金を扱うには不十分です。

ほぼすべての言語で機能する解決策は、代わりに整数を使用してセントを数えることです。たとえば、1025は$ 10.25になります。いくつかの言語には、金銭に対処するための組み込み型もあります。特に、JavaにはBigDecimalクラスがあり、C#にはdecimalタイプがあります。


3
@Fran丸めエラーが発生し、大量の通貨が使用されている場合、金利計算が大幅にオフになる可能性があります
linuxuser27

5
...ほとんどの基本10分数、つまりです。たとえば、0.1には正確な2進浮動小数点表現がありません。したがって、1.0 / 10 * 101.0と同じではない可能性があります。
Chris Jester-Young

6
@ linuxuser27フランは面白くしようとしていたと思います。とにかく、zneakの答えは私が見た中で最高で、Blochのクラシックバージョンよりも優れています。
Isaac Rabinovitch、2012年

5
もちろん、精度がわかっていれば、常に結果を丸めることができるため、問題全体を回避できます。これはBigDecimalを使用するよりもはるかに高速で簡単です。もう1つの方法は、固定精度のintまたはlongを使用することです。
Peter Lawrey、2013

2
@zneak有理数が実数のサブセットであることを知っていますか?IEEE-754実数実数です。彼らは偶然に合理的です。
Tim Seguine 2014年

314

Bloch、J.、Effective Java、第2版、アイテム48から:

floatそしてdouble、0.1(又は10の任意の他の負のパワー)を表すことは不可能であるため、種類は特に金融計算のために不適当であるfloatか、 double正確。

たとえば、$ 1.03があり、42cを使っているとします。いくらお金が残っていますか。

System.out.println(1.03 - .42);

印刷し0.6100000000000001ます。

この問題を解決する正しい方法はBigDecimalintまたはを使用long して金額を計算することです。

BigDecimalただし、いくつかの注意点があります(現在受け入れられている回答を参照してください)。


6
通貨の計算にintまたはlongを使用するという推奨事項に少し混乱しています。1.03をintまたはlongとしてどのように表現しますか?「long a = 1.04;」を試しました。そして「長いa = 104/100;」無駄に。
Peter

49
@ピーター、あなたlong a = 104はドルの代わりにセントで使用して数えます。
zneak 2014年

@zneak複利などの割合を適用する必要がある場合はどうですか?
trusktr 2016年

3
@trusktr、私はあなたのプラットフォームの10進数型で行きます。Javaでは、それはBigDecimalです。
zneak 2016年

13
@maaartinus ...そして、そのようなものにdoubleを使用することはエラーが発生しやすいとは思わないのですか?私は、フロート丸め問題が実際のシステムを打つ見てきましたハード。銀行でも。推奨しないでください。推奨する場合は、別の回答として提供してください(反対投票が可能です:P)
eis

75

これは正確さの問題ではなく、正確さの問題でもありません。それは、2の代わりに10を基に計算を行う人間の期待に応えることです。たとえば、財務計算にdoubleを使用しても、数学的な意味で「間違った」答えは生成されませんが、財政的な意味で期待されるものではありません。

出力の直前に結果を四捨五入しても、期待と一致しないdoubleを使用して結果が時々得られることがあります。

電卓を使用するか、手動で結果を計算すると、1.40 * 165 = 231になります。ただし、内部的にdoubleを使用しているため、私のコンパイラ/オペレーティングシステム環境では、230.99999に近い2進数として保存されます。そのため、数値を切り捨てると、231ではなく230になります。切り捨てではなく丸めを行うと、これは正しい結果ですが、丸めは常に切り捨てを伴います。どのような丸め手法を使用しても、切り上げが予想されるときに切り捨てられるこのような境界条件がまだあります。それらは非常にまれであるため、通常のテストや観察では発見されないことがよくあります。期待どおりに動作しない結果を示す例を検索するために、コードを記述する必要がある場合があります。

何かを最も近いペニーに四捨五入したいとします。したがって、最終結果を取得し、100を掛けて0.5を加え、切り捨ててから、結果を100で除算してペニーに戻します。保存した内部番号が3.465の代わりに3.46499999 ....だった場合、数値を最も近いペニーに丸めると、3.47ではなく3.46になります。しかし、ベース10の計算では、答えが正確に3.465である必要があることが示されている可能性があります。財務計算にdoubleを使用すると、このようなことが時々起こります。これはまれなので、問題として気付かれないことがよくありますが、それは起こります。

内部計算にdoubleではなくbase 10を使用する場合、コードに他のバグがないことを前提として、答えは常に人間が期待するものとまったく同じです。


2
関連、興味深い:私のChrome jsコンソール:Math.round(.4999999999999999):0 Math.round(.49999999999999999):1
Curtis Yallop

16
この答えは誤解を招くものです。正確に231以外1.40 * 165 = 231任意の数であり、数学的な意味(および他のすべての感覚)で間違いました。
Karu

2
@KaruだからRandyはフロートが悪いと言っています...私のChrome JSコンソールは結果として230.99999999999997を表示します。それ間違っています、それが答えでなされたポイントです。
trusktr 2016年

6
@カル:Imho答えは数学的に間違っていません。2つの質問があり、1つは回答されており、これは質問されている質問ではありません。コンパイラが答える質問は1.39999999 * 164.99999999で、数学的に正しいものは230.99999です。明らかに、そもそも質問された質問ではありません...
markus

2
@CurtisYallopの0.49999999999999999に近いdouble値は0.5なので、なぜMath.round(0.49999999999999994)1を返すのですか?
phuclv 2018

53

私はこれらの応答のいくつかに困っています。倍精度浮動小数点数は、財務計算に影響があると思います。確かに、非小数の金額を加算および減算する場合、整数クラスまたはBigDecimalクラスを使用しても精度は失われません。しかし、より複雑な演算を実行すると、数値の格納方法に関係なく、多くの場合、結果は小数点以下の桁数が多くなります。問題は、結果をどのように提示するかです。

結果が切り上げと切り捨ての境界にあり、その最後のペニーが本当に重要である場合、おそらく小数点以下の桁数を増やして、答えがほぼ真ん中であることを視聴者に伝える必要があります。

doubleの問題、さらにはfloatの問題は、doubleを使用して大きな数と小さな数を組み合わせる場合です。Javaでは、

System.out.println(1000000.0f + 1.2f - 1000000.0f);

結果は

1.1875

16
この!!!!私はこの関連事実を見つけるためにすべての答えを探していました!!! 通常の計算では、あなたが1セントの何分の1かを気にしている人は誰もいませんが、ここでは数が多いと、トランザクションごとに簡単にいくつかのドルが失われます!
ファルコ2014年

22
そして、誰かが100万ドルで0.01%の毎日の収入を得ていると想像してください-彼は毎日何も得ません-そして1年後、彼は1000ドルを獲得していません、これは問題になります
Falco

6
問題は精度ではありませんが、そのフロートは不正確になったことを通知しません。整数は最大10桁しか保持できません。浮動小数点は最大6桁まで保持できます(それに応じて切り捨てた場合)。整数がオーバーフローし、Javaなどの言語が警告するか許可しない場合に、これは許可されます。doubleを使用する場合、最大16桁まで使用でき、多くの使用例で十分です。
sigi 2016年

39

floatとdoubleは概算です。BigDecimalを作成し、フロートをコンストラクターに渡すと、フロートが実際に何と等しいかがわかります。

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

これはおそらく$ 1.01を表現する方法ではありません。

問題は、IEEE仕様にすべての分数を正確に表す方法がないことです。一部の分数は繰り返し分数になり、近似エラーが発生します。会計士は物事が正確に1セントまで出てくることを好み、顧客が請求書を支払い、支払いが処理された後、.01を支払う必要があり、手数料を請求されるか、アカウントを閉鎖できないため、使用することをお勧めします。 10進数(C#)またはJavaのjava.math.BigDecimalのような正確な型。

丸めてもエラーを制御できないわけではありません。PeterLawreyによるこの記事を参照してください。そもそも丸める必要がないほうが簡単です。お金を扱うほとんどのアプリケーションは、多くの計算を必要としません。操作は、物を追加するか、異なるバケットに金額を割り当てることで構成されます。浮動小数点の導入と丸めは、物事を複雑にするだけです。


5
floatdoubleおよびBigDecimal正確な値を表します。コードからオブジェクトへの変換は、他の操作と同様に不正確です。タイプ自体は不正確ではありません。
chux-モニカを

1
@chux:これをもう一度読んで、私の言い回しを改善できると思います。これを編集して書き直します。
Nathan Hughes

28

私は反対票を投じられる危険を冒しますが、浮動小数点数が通貨計算に不適切であることは過大評価されていると思います。zneakによって説明される2進数の10進数表現の不一致に対抗するために、セントの丸めを正しく行い、処理するのに十分な有効桁数があることを確認する限り、問題は発生しません。

Excelで通貨を使用して計算する人は、常に倍精度浮動小数点数を使用しており(Excelには通貨タイプはありません)、丸めエラーについて不平を言う人はまだいません。

もちろん、あなたは理性の範囲内に留まる必要があります。たとえば、単純なWebショップではおそらく倍精度浮動小数点数で問題が発生することはないでしょうが、たとえば会計など、大量の(制限されていない)数値を追加する必要がある場合は、10フィートで浮動小数点数に触れたくないでしょう。ポール。


3
これは実際にはかなりまともな答えです。ほとんどの場合、それらを使用することは完全に問題ありません。
Vahid Amiri 2018

2
ほとんどの投資銀行は、ほとんどのC ++プログラムと同様にdoubleを使用することに注意してください。一部は長く使用しますが、スケールの追跡に固有の問題があります。
Peter Lawrey 2018

20

浮動小数点型は近似的に10進データしか表現できないことは事実ですが、数値を表示する前に必要な精度に丸めれば、正しい結果が得られることも事実です。通常。

通常、double型は精度が16桁未満であるためです。より高い精度が必要な場合は、適切なタイプではありません。また、近似値が累積する可能性があります。

固定小数点演算を使用する場合でも、数値を丸める必要がありますが、BigIntegerとBigDecimalが周期的な10進数を取得する場合にエラーを返すというのはそうではありませんでした。したがって、ここにも近似があります。

たとえば、歴史的に財務計算に使用されているCOBOLの最大精度は18桁です。したがって、多くの場合、暗黙的な丸めが行われます。

結論として、私の意見では、倍精度は16桁の精度にはほとんど適していません。近似ではないため、不十分な場合があります。

次のプログラムの次の出力を検討してください。doubleを丸めた後、精度が16になるまでBigDecimalと同じ結果が得られることを示しています。

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}

15

浮動小数点数の結果は正確ではないため、近似ではなく正確な結果を必要とする財務計算には適していません。floatとdoubleは工学および科学計算用に設計されており、正確な結果が得られない場合が多く、浮動小数点計算の結果はJVMによって異なる場合があります。金額の値を表すために使用されるBigDecimalおよびdoubleプリミティブの以下の例を見てください。浮動小数点の計算は正確ではない可能性があり、財務計算にはBigDecimalを使用する必要があります。

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

出力:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

3
ささいな足し算/引き算と整数の相乗作用以外のものを試してみましょう。コードが7%ローンの月間レートを計算した場合、どちらのタイプも正確な値を提供できず、最も近い0.01に丸める必要があります。最小の通貨単位への丸めは、お金の計算の一部です。10進数型を使用すると、加算/減算でその必要性を回避できますが、それ以外はそれほど多くありません。
chux-モニカを2015年

@ chux-ReinstateMonica:利息が毎月複利になると想定される場合は、毎日の残高を合計して利息を計算し、7(金利)を掛けて、最も近いペニーに丸めて、日数で割ります年。最後のステップで1か月に1回を除いて、どこにも丸めません。
スーパーキャット

@supercat私のコメントでは、最小の通貨単位のバイナリFPまたは10進数FPを使用すると、どちらも同様の丸めの問題が発生します-「除算、最も近いペニーへの丸め」のコメントのように。基数2または基数10のFPを使用しても、シナリオではどちらの利点も得られません。
chux-モニカを

@ chux-ReinstateMonica:上記のシナリオで、数学が半分のセントの数と正確に等しくなければならないことが数学で判明した場合、正しい金融プログラムは正確に指定された方法で四捨五入する必要があります。浮動小数点計算の利息値がたとえば$ 1.23499941であるが、丸め前の数学的に正確な値が$ 1.235であり、丸めが「最も近い偶数」として指定されている場合、そのような浮動小数点計算を使用しても結果は発生しません。 0.000059ドルずれるが、全体として0.01ドルずれる。これは、会計目的では単なる誤りである。
スーパーキャット

@supercat 2進doubleFPをセントに使用しても、10進FPとは異なり、0.5セントまで計算しても問題はありません。浮動小数点の計算により、2進FPまたは10進FPを介して、たとえば123.499941¢の利息値が得られる場合、二重丸めの問題は同じです-どちらの方法にも利点はありません。あなたの前提は、数学的に正確な値と10進FPが同じであると想定しているようです。10進FPでも保証されません。0.5 / 7.0 * 7.0は、バイナリおよびデシマルFPの問題です。IAC、Cの次のバージョンが10進数のFPを提供することを期待しているので、ほとんどは無意味です。
chux-モニカを

11

先に述べたように、「ソフトウェアが小さなエラーを四捨五入するので、最初にダブルまたはフロートとしてお金を表すことはおそらく見栄えがよくなりますが、不正確な数値でより多くの加算、減算、乗算、および除算を実行すると、ますます精度が失われます。エラーが増えると、浮動小数点数と倍精度浮動小数点数は、基本10の累乗の倍数の完全な精度が必要とされる金銭を処理するには不十分になります。」

最後に、JavaにはCurrency And Moneyを操作する標準的な方法があります。

JSR 354:Money and Currency API

JSR 354は、MoneyとCurrencyを使用した包括的な計算を表現、転送、および実行するためのAPIを提供します。次のリンクからダウンロードできます。

JSR 354:Money and Currency APIダウンロード

仕様は次のもので構成されています。

  1. 金額や通貨などを処理するためのAPI
  2. 交換可能な実装をサポートするAPI
  3. 実装クラスのインスタンスを作成するためのファクトリー
  4. 金額の計算、変換、およびフォーマットの機能
  5. Money 9に含まれる予定のMoneyと通貨を操作するためのJava API。
  6. すべての仕様クラスとインターフェースは、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のコレクションを操作する場合、フィルタリング、並べ替え、およびグループ化に役立つユーティリティメソッドがいくつか利用できます。

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

リソース:

JSR 354を使用してJavaでお金と通貨を処理する

Java 9 Money and Currency API(JSR 354)の調査

参照:JSR 354-通貨とお金


5

計算にさまざまなステップが含まれる場合、任意精度の計算では100%をカバーできません。

結果の完全な表現を使用する唯一の信頼できる方法(最後のステップに除算演算をバッチ処理するカスタムFractionデータ型を使用)および最後のステップでのみ10進表記に変換します。

小数点以下の桁数が多い数値や、次のような結果が常に存在する可能性があるため、任意の精度では効果が0.6666666ありません。最後の例では、任意の表現ではカバーできません。そのため、各ステップで小さなエラーが発生します。

これらのエラーは追加され、最終的には無視できなくなる可能性があります。これはエラー伝播と呼ばれます。


4

ほとんどの回答は、お金と通貨の計算にダブルを使用してはならない理由を強調しています。そして私はそれらに完全に同意します。

ただし、その目的でdoubleを使用することはできません。

私はgc要件が非常に低い多くのプロジェクトに取り組んできましたが、BigDecimalオブジェクトがあることがそのオーバーヘッドの大きな原因でした。

この賢明な提案をもたらすのは、二重表現についての理解の欠如と、精度と精度を処理する経験の欠如です。

プロジェクトの精度と精度の要件を処理できる場合は、それを機能させることができます。これは、処理するdouble値の範囲に基づいて行う必要があります。

詳細については、グアバのFuzzyCompareメソッドを参照してください。パラメータの許容誤差が重要です。証券取引アプリケーションでこの問題に対処し、さまざまな範囲のさまざまな数値に使用する許容値について徹底的な調査を行いました。

また、実装であるハッシュマップを使用して、マップキーとしてDoubleラッパーを使用したい場合があります。Double.equalsとハッシュコードの例の値「0.5」と「0.6-0.1」は大きな混乱を引き起こすため、非常に危険です。


2

この質問に投稿された回答の多くは、IEEEと浮動小数点演算に関する標準について説明しています。

コンピューターサイエンス以外の背景(物理学と工学)の出身なので、問題を別の視点から見る傾向があります。私にとって、数学的計算でdoubleまたはfloatを使用しない理由は、情報を失いすぎるためです。

代替案は何ですか?たくさんあります(そして私が知らないもっとたくさんの!)。

JavaのBigDecimalは、Java言語にネイティブです。Apfloatは、Java用の別の任意精度ライブラリです。

C#のdecimalデータ型は、有効数字28桁のMicrosoftの.NET代替です。

SciPy(Scientific Python)は、おそらく財務計算も処理できます(私は試していませんが、そうだと思います)。

GNU Multiple Precision Library(GMP)とGNU MFPR Libraryは、CおよびC ++用の2つの無料のオープンソースリソースです。

JavaScript(!)用の数値精度ライブラリもあり、PHPは財務計算を処理できると思います。

また、多くのコンピューター言語向けのプロプライエタリ(特に、Fortran向け)とオープンソースのソリューションもあります。

私は訓練を受けたコンピュータ科学者ではありません。ただし、私はJavaのBigDecimalまたはC#のdecimalに傾く傾向があります。私はこれまでに挙げた他のソリューションを試していませんが、おそらく非常に良いソリューションでもあります。

私にとっては、サポートするメソッドのおかげでBigDecimalが好きです。C#の10進数はとてもいいですが、思い通りに操作する機会がありませんでした。余暇には興味のある科学計算を行っていますが、浮動小数点数の精度を設定できるため、BigDecimalは非常にうまく機能しているようです。BigDecimalの欠点?特に分割メソッドを使用している場合は、速度が遅くなることがあります。

速度を上げるために、C、C ++、およびFortranの無料の専用ライブラリを調べてみてください。


1
SciPy / Numpyに関しては、固定精度(つまり、Pythonのdecimal.Decimal)はサポートされていません(docs.scipy.org/doc/numpy-dev/user/basics.types.html)。一部の関数はDecimalで正しく機能しません(たとえば、isnan)。PandasはNumpyに基づいており、主要な量的ヘッジファンドの1つであるAQRで開始されました。だからあなたは(食料品会計ではなく)財務計算に関するあなたの答えを持っています。
コント

2

以前の回答を補足するために、BigDecimalのほかに、質問で取り上げられている問題を処理するときに、JavaでJoda-Moneyを実装するオプションもあります。Javaモジュール名はorg.joda.moneyです。

Java SE 8以降が必要で、依存関係はありません。

より正確には、コンパイル時の依存関係がありますが、必須ではありません。

<dependency>
  <groupId>org.joda</groupId>
  <artifactId>joda-money</artifactId>
  <version>1.0.1</version>
</dependency>

Joda Moneyの使用例:

  // create a monetary value
  Money money = Money.parse("USD 23.87");

  // add another amount with safe double conversion
  CurrencyUnit usd = CurrencyUnit.of("USD");
  money = money.plus(Money.of(usd, 12.43d));

  // subtracts an amount in dollars
  money = money.minusMajor(2);

  // multiplies by 3.5 with rounding
  money = money.multipliedBy(3.5d, RoundingMode.DOWN);

  // compare two amounts
  boolean bigAmount = money.isGreaterThan(dailyWage);

  // convert to GBP using a supplied rate
  BigDecimal conversionRate = ...;  // obtained from code outside Joda-Money
  Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);

  // use a BigMoney for more complex calculations where scale matters
  BigMoney moneyCalc = money.toBigMoney();

ドキュメント:http : //joda-money.sourceforge.net/apidocs/org/joda/money/Money.html

実装例:https : //www.programcreek.com/java-api-examples/?api=org.joda.money.Money


0

いくつかの例...これはほとんどすべてのプログラミング言語で機能します(実際には期待どおりに機能しません)...私はDelphi、VBScript、Visual Basic、JavaScript、そして今やJava / Androidで試しました:

    double total = 0.0;

    // do 10 adds of 10 cents
    for (int i = 0; i < 10; i++) {
        total += 0.1;  // adds 10 cents
    }

    Log.d("round problems?", "current total: " + total);

    // looks like total equals to 1.0, don't?

    // now, do reverse
    for (int i = 0; i < 10; i++) {
        total -= 0.1;  // removes 10 cents
    }

    // looks like total equals to 0.0, don't?
    Log.d("round problems?", "current total: " + total);
    if (total == 0.0) {
        Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
    } else {
        Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
    }

出力:

round problems?: current total: 0.9999999999999999 round problems?: current total: 2.7755575615628914E-17 round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!


3
問題は、丸め誤差が発生することではありませんが、対処しないことです。結果を小数点以下2桁に丸め(セントが必要な場合)、これで完了です。
maaartinus 2017年

0

Floatは、Decimalのバイナリ形式で、デザインが異なります。それらは2つの異なるものです。2つのタイプを相互に変換した場合、エラーはほとんどありません。また、floatは、科学的に無限大の値を表すように設計されています。つまり、その固定バイト数では、非常に小さい数から非常に大きい数に精度を失うように設計されています。Decimalは無限の数の値を表すことはできず、その数の10進数字に制限されます。したがって、FloatとDecimalは目的が異なります。

通貨価値のエラーを管理する方法はいくつかあります。

  1. 代わりに長整数を使用し、セントでカウントしてください。

  2. 倍精度を使用し、有効桁数を15のみに維持して、10進数を正確にシミュレーションできるようにします。値を表示する前に丸めます。計算を行うときに頻繁に丸めます。

  3. Java BigDecimalなどの10進数ライブラリを使用して、10進数をシミュレートするためにdoubleを使用する必要がないようにします。

psほとんどのブランドのハンドヘルド関数電卓が浮動小数点数ではなく小数で動作することを知るのは興味深いことです。したがって、フロート変換エラーはありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.