Java の丸め二重の問題を解決する方法 [重複]
質問
この質問にはすでに答えがあります:
- Java で double を使用して精度を維持する 20 件の回答
減算によって何らかの問題が発生し、結果の値が間違っているようです。
double tempCommission = targetPremium.doubleValue()*rate.doubleValue()/100d;
78.75 = 787.5 * 10.0/100日
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 になります。
正しい計算を保証するにはどうすればよいですか?
解決
浮動小数点演算の精度を制御するには、 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
他のヒント
前の回答で述べたように、これは浮動小数点演算を行った結果です。
以前のポスターが示唆したように、数値計算を行うときは、java.math.BigDecimal
を使用します。
ただし、BigDecimal
の使用には注意が必要です。 double値からBigDecimal(double)
に変換する場合、新しいBigDecimal.valueOf(double)
コンストラクターまたはdouble
静的ファクトリーメソッドを使用することを選択できます。静的ファクトリーメソッドを使用します。
doubleコンストラクターはString
の精度全体を<=>に変換しますが、静的ファクトリーはそれを<=>に効果的に変換してから、<=>に変換します。
これは、これらの微妙な丸めエラーが発生した場合に重要になります。数値は.585と表示される場合がありますが、内部的にはその値は「0.58499999999999996447286321199499070644378662109375」です。 <=>コンストラクタを使用した場合、0.585に等しくない数値を取得しますが、静的メソッドは0.585に等しい値を提供します。
double value = 0.585; System.out.println(new BigDecimal(value)); System.out.println(BigDecimal.valueOf(value));
私のシステムでは
0.58499999999999996447286321199499070644378662109375 0.585
別の例:
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は、少なくともこの点で一貫した動作を保証します。
上記の例を次のように変更します。
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);
これらのオプションは、doubleを使用するよりも優れていると思います。 webappsでは、数字は文字列としてとにかく始まります。
doubleを使用して計算を行うと、いつでもこれが起こります。このコードは877.85を提供します:
double answer = Math.round(dCommission * 100000)/ 100000.0;
ドルではなくセントの数を保存し、出力時にフォーマットをドルに変換します。そうすれば、精度の問題の影響を受けない整数を使用できます。
この質問への回答をご覧ください。基本的に、あなたが見ているのは、浮動小数点演算を使用した自然な結果です。
任意の精度(入力の有効桁数)を選択し、それを快適に実行できる場合は、結果をそれに丸めることができます。
これは楽しい問題です。
Timons の返信の背後にある考え方は、有効な double の最小精度を表すイプシロンを指定するというものです。アプリケーションで 0.00000001 未満の精度が必要ないことがわかっている場合は、真実に非常に近い、より正確な結果を得るには彼の提案で十分です。最大精度が事前にわかっているアプリケーションで役立ちます (たとえば、金融の通貨精度など)。
ただし、四捨五入しようとする根本的な問題は、係数で割ってスケールを変更すると、実際には精度の問題が別の可能性を引き起こすことです。double を操作すると、さまざまな頻度で不精度の問題が発生する可能性があります。特に、非常に有効な桁で丸めようとしている場合 (つまり、オペランドが 0 未満である場合)、たとえば Timons コードで次のコードを実行する場合です。
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 を使用して再スケーリングする最後のステップは、最後の除算でエラーが再導入される可能性のある入力値がないと判断できるユースケースによっては、必ずしも必要ではないことが指摘されています。現時点ではこれを適切に判断する方法がわからないので、誰かがその方法を知っている場合は、それについてお知らせいただけると幸いです。
これまで、Javaでこれを行う最もエレガントで効率的な方法:
double newNum = Math.floor(num * 100 + 0.5) / 100;
さらに良いのは、 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
double rounded = Math.rint(toround * 100) / 100;
正確な計算にはdoubleを使用しないでくださいが、結果を丸める場合は、次のトリックが役立ちました。
public static int round(Double i) {
return (int) Math.round(i + ((i > 0.0) ? 0.00000001 : -0.00000001));
}
例:
Double foo = 0.0;
for (int i = 1; i <= 150; i++) {
foo += 0.00010;
}
System.out.println(foo);
System.out.println(Math.round(foo * 100.0) / 100.0);
System.out.println(round(foo*100.0) / 100.0);
どの印刷:
0.014999999999999965
0.01
0.02
非常に簡単です。
出力には%.2f演算子を使用します。問題は解決しました!
例:
int a = 877.8499999999999;
System.out.printf("Formatted Output is: %.2f", a);
上記のコードは、次の印刷出力になります。 877.85
%。2f演算子は、小数点以下2桁のみを使用することを定義しています。