3

この質問がすでに何度か議論されていることは知っていますが、私はその答えに完全に満足しているわけではありません. 「倍精度は不正確です。0.1 を表すことはできません! BigDecimal を使用する必要があります」...

基本的に私は金融ソフトウェアをやっており、多くの価格をメモリに保存する必要がありました。BigDecimal は大きすぎてキャッシュに収まらないため、double に切り替えることにしました。これまでのところ、正当な理由でバグは発生しておらず、12 桁の精度が必要です。12 桁の見積もりは、100 万単位で話している場合でも、セントを扱うことができるという事実に基づいています。

double は、10 進数の有効桁数 15 桁の精度を示します。ダブルを表示/比較する必要があるときにダブルを丸めると、何がうまくいかないのでしょうか??

問題は不正確さの蓄積だと思いますが、それはどれほど悪いですか?12 桁目に影響を与えるまでに何回の操作が必要ですか?

ダブルスに関して他に問題はありますか?

編集:長い間、それは間違いなく私たちが考えてきたことです. 私たちは多くの除算乗算を行っていますが、long はそれをうまく処理できません (10 進数の損失とオーバーフロー)。私の質問は、ダブルスの理論に関するものです。基本的に、それはどれほど悪いのでしょうか。また、不正確さは許容できますか?

EDIT2:私のソフトウェアを解決しようとしないでください、私は不正確で大丈夫です:)。質問を言い換えます: 12 桁しか必要とせず、表示/比較するときに 2 倍を四捨五入すると、不正確になる可能性はどのくらいありますか?

4

8 に答える 8

13

絶対に sBigDecimalを使用できず、使用したくないdouble場合は、s を使用して固定小数点演算を行います(たとえば、各値がセント数を表すようにします)。これにより、18 桁の有効数字を表すことができます。 longlong

joda-moneyを使用すると思いますが、これは裏で使用BigDecimalされます。


編集(上記は実際には質問に答えていないため):

免責事項: 正確さが重要な場合は、money を使用しないでくださいdouble。しかし、投稿者は正確な正確さを必要とせず (これは、おそらく 10**-12 を超える不確実性が組み込まれている金融価格設定モデルに関するものと思われます)、パフォーマンスを重視しているようです。これが事実であると仮定すると、 a を使用することdoubleは許されます。

一般に、adoubleは小数を正確に表すことはできません。では、 a はどれほど不正確なのdoubleでしょうか? これには簡単な答えはありません。

Aは、数値を aに読み取ってから再度書き戻して、小数点以下 15 桁の精度を保持doubleできるほど十分に数値を表すことができる場合があります。doubleしかし、これは小数ではなく 2 進数であるため、正確に表すことはできません。これは、表現したい値であり、プラスまたはマイナスの誤差があります。不正確な s を含む多くの算術演算が実行されるdoubleと、最終的な結果の精度が 10 進数で 15 桁未満になるように、この誤差の量が時間の経過とともに増加する可能性があります。何個少ない?場合によります。

n1000 の th ルートを取り、それ自体をn何回も乗算する次の関数を考えてみましょう。

private static double errorDemo(int n) {
    double r = Math.pow(1000.0, 1.0/n);
    double result = 1.0;
    for (int i = 0; i < n; i++) {
        result *= r;
    }
    return 1000.0 - result;
}

結果は次のとおりです。

errorDemo(     10) = -7.958078640513122E-13
errorDemo(     31) = 9.094947017729282E-13
errorDemo(    100) = 3.410605131648481E-13
errorDemo(    310) = -1.4210854715202004E-11
errorDemo(   1000) = -1.6370904631912708E-11
errorDemo(   3100) = 1.1107204045401886E-10
errorDemo(  10000) = -1.2255441106390208E-10
errorDemo(  31000) = 1.3799308362649754E-9
errorDemo( 100000) = 4.00075350626139E-9
errorDemo( 310000) = -3.100740286754444E-8
errorDemo(1000000) = -9.706695891509298E-9

累積された不正確さのサイズは、中間ステップの数に正確に比例して増加しないことに注意してください (実際、単調に増加するわけではありません)。既知の一連の中間操作が与えられると、不正確さの確率分布を決定できます。これは操作が多いほど範囲が広くなりますが、正確な量は計算に入力される数値によって異なります。不確実性はそれ自体不確実です!

実行している計算の種類によっては、中間ステップの後で単位全体またはセント単位に丸めることで、このエラーを制御できる場合があります。double(毎月複利で年利 6% の $100 を保有する銀行口座の場合を考えてみましょう。したがって、月あたりの利子は 0.5% です。3 か月目の利子が入金された後、残高を $ 101.50または $101.51 にしたいですか?)全体の単位数よりも分数単位 (つまり、セント) の方が簡単になりますが、そうする場合は、long上で提案したように s を使用することもできます。

免責事項、繰り返しますが、浮動小数点エラーの累積により、doubles を使用して金額を計算すると、非常に面倒になる可能性があります。何年にもわたって叩きつけられたものを 10 進表現に使用するという弊害を経験してきた Java 開発者として言えば、doubleお金に関する重要な計算には浮動小数点演算ではなく 10 進法を使用します。

于 2013-11-14T13:04:03.100 に答える
6

Martin Fowler は、そのトピックについて何かを書きました。彼は、内部 long 表現と小数係数を持つ Money クラスを提案しています。 http://martinfowler.com/eaaCatalog/money.html

于 2013-11-14T13:04:25.037 に答える
6

固定小数点 (整数) 演算を使用しないと、計算が常に正しいことを確認できません。これは、IEEE 754浮動小数点表現の仕組みが原因で、一部の 10 進数は有限長の 2 進小数として表現できません。ただし、すべての固定小数点数は有限長の整数として表すことができます。したがって、正確なバイナリ値として格納できます。

次の点を考慮してください。

public static void main(String[] args) {
    double d = 0.1;
    for (int i = 0; i < 1000; i++) {
        d += 0.1;
    }
    System.out.println(d);
}

これは印刷され100.09999999999859ます。sを使用したマネーの実装はすべて失敗します。 double

より視覚的な説明については、10 進数から 2 進数へのコンバーターをクリックして、0.1 を 2 進数に変換してみてください。0.00011001100110011001100110011001 (0011 繰り返し) になり、10 進数に戻すと 0.0999999998603016138 になります。

したがって、0.1 == 0.0999999998603016138


補足として、BigDecimal は、小数点以下の位置が int の単純な BigInteger です。BigInteger は基になる int[] に依存してその桁を保持するため、固定小数点精度が提供されます。

public static void main(String[] args) {
    double d = 0;
    BigDecimal b = new BigDecimal(0);
    for (long i = 0; i < 100000000; i++) {
        d += 0.1;
        b = b.add(new BigDecimal("0.1"));
    }
    System.out.println(d);
    System.out.println(b);
}

出力:
9999999.98112945 (10^8 加算後、1 セントが失われます)
10000000.0

于 2013-11-14T13:22:51.440 に答える
2

歴史的に、2^32 より大きくなる可能性があるが 2^52 より大きくならない整数の正確な計算に浮動小数点型を使用することはしばしば合理的でした [または、適切な「long double」型を持つマシンでは、2^ 64]。52 ビットの数値を 32 ビットの数値で割って 20 ビットの商を求めるには、8088 ではかなり長いプロセスが必要になりますが、8087 プロセッサでは比較的迅速かつ簡単に実行できます。正確である必要があるすべての値が常に整数で表されていれば、財務計算に小数を使用することは完全に合理的でした。

今日では、コンピューターはより大きな整数値を効率的に処理できるようになり、その結果、整数で表される量を処理するために整数を使用する方が一般的に理にかなっています。浮動小数点数は分数の割り算などには便利に思えるかもしれませんが、正しいコードでは、何を行うにしても整数への丸めの影響に対処する必要があります。3 人が 100.00 ドルかかるものを支払う必要がある場合、全員に 33.333333333333 を支払わせても、正確な会計を達成することはできません。物事をバランスさせる唯一の方法は、人々に不平等な金額を支払わせることです。

于 2013-11-21T22:14:58.017 に答える
1

のサイズがBigDecimalキャッシュに対して大きすぎる場合はlong、キャッシュに書き込まれるときに金額を値に変換し、BigDecimal読み取られるときに元の値に戻す必要があります。これにより、キャッシュのメモリ フットプリントが小さくなり、アプリケーションで正確な計算が行われます。

計算への入力を double で正しく表すことができたとしても、常に正確な結果が得られるとは限りません。あなたはまだキャンセルやその他のことで苦しむことができます.

アプリケーションロジックでの使用を拒否すると、すでに提供BigDecimalされている多くの機能を書き直すことになります。BigDecimal

于 2013-11-14T13:16:14.063 に答える
-1

Joshua Bloch Java Puzzlers Traps Pitfalls を読んでいただければ幸いです。これは、パズル 2 で彼が言ったことです。変化の時です。

2 進浮動小数点数は、0.1 またはその他の 10 の負の累乗を有限長の 2 進小数として正確に表すことができないため、通貨の計算には特に適していません [EJ Item 31]。

于 2013-11-14T13:07:32.250 に答える
-1

金融ソフトウェアでダブルスを信頼することはできません。単純なケースではうまく機能するかもしれませんが、丸め、特定の値の不正確さなどにより、問題が発生します。

を利用するしかありませんBigDecimal。そうでなければ、「私はほとんど機能する金融コードを書いています。矛盾にほとんど気付かないでしょう」と言っているのです。それはあなたを信頼できるように見せるものではありません。

固定小数点は特定のケースで機能しますが、現在および将来において 1 セントの精度で十分であると確信できますか?

于 2013-11-14T13:04:57.690 に答える