web-dev-qa-db-ja.com

Java Rounding Doubleの問題を解決する方法

減算が何らかの問題を引き起こしており、結果の値が間違っているようです。

double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;

78.75 = 787.5 * 10.0/100d

double netToCompany = targetPremium.doubleValue() - tempCommission;

708.75 = 787.5-78.75

double dCommission = request.getPremium().doubleValue() - netToCompany;

877.8499999999999 = 1586.6-708.75

結果の期待値は877.85になります。

正しい計算を保証するために何をすべきですか?

73
Patrick

浮動小数点演算の精度を制御するには、 Java.math.BigDecimal を使用する必要があります。詳細については、John Zukowskiによる BigDecimalの必要性 を参照してください。

あなたの例を考えると、最後の行はBigDecimalを使用して次のようになります。

import Java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf("1586.6");
BigDecimal netToCompany = BigDecimal.valueOf("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

これにより、次の出力が生成されます。

877.85 = 1586.6 - 708.75
93
Eric Weilnau

前の回答で述べたように、これは浮動小数点演算を行った結果です。

前のポスターが示唆したように、数値計算を行うときは_Java.math.BigDecimal_を使用します。

ただし、BigDecimalの使用には注意が必要です。 double値からBigDecimalに変換する場合、新しいBigDecimal(double)コンストラクターまたはBigDecimal.valueOf(double)静的ファクトリーメソッドを選択できます。静的ファクトリーメソッドを使用します。

Doubleコンストラクターはdoubleの精度全体をBigDecimalに変換しますが、静的ファクトリーはそれをStringに効果的に変換してから、BigDecimalに変換します。

これは、これらの微妙な丸めエラーが発生した場合に関連します。数値は.585として表示される場合がありますが、内部的にはその値は「0.58499999999999996447286321199499070644378662109375」です。 BigDecimalコンストラクターを使用した場合、0.585に等しくない数値を取得しますが、静的メソッドは0.585に等しい値を返します。

 double value = 0.585; 
 System.out.println(new BigDecimal(value)); 
 System.out.println(BigDecimal.valueOf(value)); 

私のシステムでは

 0.58499999999999996447286321199499070644378662109375 
 0.585 
65
Johann Zacharee

もう一つの例:

double d = 0;
for (int i = 1; i <= 10; i++) {
    d += 0.1;
}
System.out.println(d);    // prints 0.9999999999999999 not 1.0

代わりにBigDecimalを使用してください。

編集:

また、これは単に「Java」の丸めの問題ではありません。他の言語も同様の動作を示します(必ずしも一貫しているとは限りません)。 Javaは、少なくともこの点で一貫した動作を保証します。

10
toolkit

上記の例を次のように変更します。

import Java.math.BigDecimal;

BigDecimal premium = new BigDecimal("1586.6");
BigDecimal netToCompany = new BigDecimal("708.75");
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

これにより、文字列を使用して開始する際の落とし穴を回避できます。別の選択肢:

import Java.math.BigDecimal;

BigDecimal premium = BigDecimal.valueOf(158660, 2);
BigDecimal netToCompany = BigDecimal.valueOf(70875, 2);
BigDecimal commission = premium.subtract(netToCompany);
System.out.println(commission + " = " + premium + " - " + netToCompany);

これらのオプションはダブルを使用するよりも優れていると思います。とにかくwebappsの数字は文字列として始まります。

8
dog

Doubleを使用して計算を行うと、これが発生する可能性があります。このコードは、877.85を提供します。

二重回答= Math.round(dCommission * 100000)/ 100000.0;

7
Steve Klabnik

ドルではなくセントの数を保存し、出力するときにフォーマットをドルに変換します。そうすれば、精度の問題の影響を受けない整数を使用できます。

4
tloach

これは楽しい問題です。

Timonsの返信の背後にある考え方は、正当なdoubleの最小精度を表すイプシロンを指定することです。アプリケーションで0.00000001未満の精度を必要としないことがわかっている場合、彼が示唆していることは、真実に非常に近いより正確な結果を得るのに十分です。最大精度を事前に把握しているアプリケーションで役立ちます(たとえば、通貨精度のファイナンスなど)

しかし、それを四捨五入しようとすることの基本的な問題は、それを再スケーリングするために係数で除算すると、実際に精度の問題の別の可能性が生じることです。 doubleの操作は、さまざまな周波数で不正確な問題を引き起こす可能性があります。特に、Timonsコードで次を実行する場合など、非常に重要な桁で丸める(オペランドが0未満になる)場合は特に:

System.out.println(round((1515476.0) * 0.00001) / 0.00001);

結果は1499999.9999999998になります。ここでの目標は、500000単位で丸めることです(つまり、1500000が必要です)

実際、不正確さを完全に排除したことを完全に確認する唯一の方法は、BigDecimalを使用してスケールオフすることです。例えば.

System.out.println(BigDecimal.valueOf(1515476.0).setScale(-5, RoundingMode.HALF_UP).doubleValue());

イプシロン戦略とBigDecimal戦略を組み合わせて使用​​すると、精度を細かく制御できます。イプシロンであるという考え方により、非常に近くなり、BigDecimalはその後の再スケーリングによって生じる不正確さを排除します。 BigDecimalを使用すると、アプリケーションの予想パフォーマンスが低下します。

BigDecimalを使用して再スケールする最終ステップは、最終的な部門がエラーを再入力できる入力値がないと判断できるユースケースでは必ずしも必要ではないことが指摘されています。現在、私はこれを適切に決定する方法を知らないので、誰かがそれを知っていれば、それについて聞いて喜んでいるでしょう。

3
Doug

この質問 への回答をご覧ください。基本的に、あなたが見ているのは、浮動小数点演算を使用する自然な結果です。

任意の精度(入力の有効数字?)を選択し、それを快適に実行できる場合は、結果をそれに丸めることができます。

3
Adam Bellaire

JScience を使用すると、BigDecimalはかなり制限されます(例:sqrt関数なし)

double dCommission = 1586.6 - 708.75;
System.out.println(dCommission);
> 877.8499999999999

Real dCommissionR = Real.valueOf(1586.6 - 708.75);
System.out.println(dCommissionR);
> 877.850000000000
2
Tomasz

これまで、Javaでこれを行う最もエレガントで効率的な方法は次のとおりです。

double newNum = Math.floor(num * 100 + 0.5) / 100;
2
Roman Kagan
double rounded = Math.rint(toround * 100) / 100;
1
Denis