web-dev-qa-db-ja.com

BigDecimal.divide中にスローされるArithmeticException

_Java.math.BigDecimal_ は、10進数で無限精度の演算を実行する必要性に対するThe Answer™であるはずだと思いました。

次のスニペットを考えてみましょう:

_import Java.math.BigDecimal;
//...

final BigDecimal one = BigDecimal.ONE;
final BigDecimal three = BigDecimal.valueOf(3);
final BigDecimal third = one.divide(three);

assert third.multiply(three).equals(one); // this should pass, right?
_

私はassertが渡されることを期待しますが、実際には実行もそこに到達しません:one.divide(three)ArithmeticExceptionをスローします!

_Exception in thread "main" Java.lang.ArithmeticException:
Non-terminating decimal expansion; no exact representable decimal result.
    at Java.math.BigDecimal.divide
_

この動作は [〜#〜] api [〜#〜] に明示的に文書化されていることがわかります:

divideの場合、正確な商は無限に長い10進展開を持つ可能性があります。たとえば、1を3で割った値です。商に非終了の10進展開があり、演算が正確な結果を返すように指定されている場合、ArithmeticExceptionがスローされます。それ以外の場合は、他の演算と同様に、除算の正確な結果が返されます。

さらにAPIを参照すると、実際にinexact除算を実行するdivideのさまざまなオーバーロードがあることがわかります。

_final BigDecimal third = one.divide(three, 33, RoundingMode.DOWN);
System.out.println(three.multiply(third));
// prints "0.999999999999999999999999999999999"
_

もちろん、今の明白な質問は「何が問題なのか???」です。 exact演算が必要な場合、BigDecimalが解決策だと思いました。財務計算用。正確にdivideさえできない場合、これはどの程度役に立ちますか?それは実際に一般的な目的を果たしますか、それとも幸運にもdivideをまったく必要としない非常にニッチなアプリケーションでのみ役立ちますか?

これが正しい答えではない場合、財務計算で正確な除算に使用できる[〜#〜] [〜#〜]は何ですか? (つまり、私は財務の専攻を持っていませんが、彼らはまだ部門を使用していますよね???)。

33

これが正しい答えではない場合、財務計算の厳密な除算に何を使用できますか? (つまり、私は財務の専攻を持っていませんが、彼らはまだ部門を使用していますよね???)。

それから私は小学校にいました1、彼らは私に1で3で割ると0.33333 ...つまりrecurringの小数が得られると教えてくれました10進数で表される数値の除算は正確ではありません。実際、固定baseについては、表現できない分数(1つの整数を別の整数で除算した結果)がありますexactlyそのベースの有限精度浮動小数点数として。 (番号には繰り返し部分があります...)

除算を含む財務計算を行う場合、haveを使用して、定期的な部分をどう処理するかを検討します。切り上げ、切り捨て、または最も近い整数などにできますが、基本的には問題を忘れないだけです。

BigDecimal javadocはこれを言っています:

BigDecimalクラスにより、ユーザーは丸め動作を完全に制御できます。丸めモードが指定されておらず、正確な結果を表現できない場合は、例外がスローされます。それ以外の場合は、適切なMathContextオブジェクトを操作に提供することにより、選択した精度と丸めモードで計算を実行できます。

言い換えると、丸めの処理をBigDecimalに指示するのはあなたの責任です。

[〜#〜] edit [〜#〜]-OPからのこれらのフォローアップに応じて。

BigDecimalは無限の繰り返し10進数をどのように検出しますか?

繰り返し10進数を明示的に検出しません。特定の操作の結果が指定された精度を使用して正確に表現できないことを単に検出します。例えば正確に表現するには、小数点の後に必要な桁数が多すぎます。

配当のサイクルを追跡し、検出する必要があります。繰り返し部分がどこにあるかなどをマークすることで、これを別の方法で処理することを選択した可能性があります。

BigDecimalを指定すると、繰り返し10進数を正確に表すことができると思います。つまり、BigRationalクラスとして。ただし、これにより、実装が複雑になり、使用するのにコストがかかります。2。また、ほとんどの人は数値が10進数で表示されることを期待しており、10進数の繰り返しの問題はその時点で繰り返されます。

要するに、この余分な複雑さとランタイムコストは、BigDecimalの一般的なユースケースには不適切です。これには財務計算が含まれます。この場合、会計規則では繰り返し小数を使用できません。


1-それは素晴らしい小学校でした...

2-除数と被除数の共通因子を削除しようとするか(計算的に高価)、またはそれらを無制限に拡張できるようにします(スペース使用量が多く、後で計算するために計算的に)。

23
Stephen C

クラスはBigDecimalではなくBigFractionalです。いくつかのコメントから、誰かがこのクラスにすべての可能な数処理アルゴリズムを組み込まなかったと不平を言いたいように思えます。金融アプリは無限小数精度を必要としません。必要な精度に完全に正確な値(通常、0、2、4、または5桁の10進数).

実際、私はdoubleを使用する多くの金融アプリケーションを扱ってきました。私はそれが好きではありませんが、そのように書かれていました(Javaでもない)。為替レートと単位換算がある場合、丸めとあざの問題の両方の可能性があります。 BigDecimalは後者を排除しますが、除算のための前者はまだ残っています。

8
Kevin Brock

有理数ではなく小数を処理する必要があり、最終的な丸め(セントなどに丸める)の前に正確な算術演算が必要な場合は、ここで少しトリックを行います。

最終的な除算が1つだけになるように、いつでも数式を操作できます。そうすれば、計算中に精度が失われることがなく、常に正しく丸められた結果が得られます。例えば

a/b + c

等しい

(a + bc) / b.
6
COME FROM

ちなみに、金融ソフトウェアを扱ってきた人からの洞察に本当に感謝しています。 BigDecimalが2倍以上の権利を主張されているのをよく聞きました

財務レポートでは、スケール= 2および ROUND_HALF_UP のalwasy BigDecimalを使用します。これは、レポートに出力されるすべての値が再現可能な結果になる必要があるためです。誰かが簡単な計算機を使用してこれをチェックした場合。

スイスでは、1または2 Rappen コインがなくなったため、0.05に丸められます。

4
stacker

の必要性はありますか

a=1/3;
b=a*3;

resulting in

b==1;

金融システムでは?私はそうは思いません。金融システムでは、計算時にどのラウンドモードとスケールを使用する必要があるかが定義されています。一部の状況では、ラウンドモードとスケールは法律で定義されています。すべてのコンポーネントは、このような定義された動作に依存できます。指定された動作を満たさないため、b == 1を返すと失敗します。これは、価格などを計算するときに非常に重要です。

これは、浮動小数点を2進数で表すためのIEEE 754仕様に似ています。コンポーネントは、情報を失うことなく「より良い」表現を最適化してはなりません。これは契約を破るからです。

2

保存を分割するには、MATHcontextを設定する必要があります。

BigDecimal bd = new BigDecimal(12.12, MathContext.DECIMAL32).divide(new BigDecimal(2)).setScale(2, RoundingMode.HALF_UP);

2
Raymond Cidad

財務計算にはBigDecimalを優先する必要があります。丸めは事業者が指定する必要があります。例えば。金額(100,00 $)は、3つのアカウントに均等に配分する必要があります。どのアカウントが余分なセントを取るビジネスルールがなければなりません。

倍精度浮動小数点数は、指数関数ではない1の分数を正確に表すことができないため、金融アプリケーションでの使用には適していません。 0.6 = 6/10 = 1 * 1/2 + 0 * 1/4 + 0 * 1/8 + 1 * 1/16 + ... = 0.1001 ... bを考慮

数学計算では、シンボリック数を使用できます。分母と分子または式全体を格納します(たとえば、この数値はsqrt(5)+3/4です)。これはJava apiの主な使用例ではないため、そこで見つけることができます。

2
tkr

Javaは分数を表現するための優れたサポートはありませんが、それが不可能であることを理解する必要があります)コンピュータで作業する場合に物事を完全に正確に保つためです。少なくともこの場合、例外は精度が失われていることを示しています。

私の知る限り、「10進数での無限精度演算」は起こりません。小数を処理する必要がある場合は、おそらく問題ありません。例外をキャッチしてください。それ以外の場合、Googleのクイック検索で、Javaで分数を処理するための興味深いリソースが見つかります。

http://commons.Apache.org/math/userguide/fraction.html

http://www.merriampark.com/fractions.htm

Javaで分数を表す最良の方法?

1
Adrian Mouat

コンピューターを使用していることに注意してください...コンピューターには多くのRAMがあり、精度はRAMを必要とします。したがって、無限の精度が必要な場合は、
(infinite * infinite) ^ (infinite * Integer.MAX_VALUE)テラバイトram ...

知っている 1 / 30.333333...そして、「1を3で割った値」のようにramに保存できるはずです。それを元に戻すと、1。しかし、私はJavaがそのようなものを持っているとは思いません...
たぶんあなたはそれをする何かを書くためにノーベル価格を獲得しなければなりません。 ;-)

0