web-dev-qa-db-ja.com

Java BigDecimalからdoubleへの変換の精度が失われます

完全にdoubleに基づくアプリケーションを使用していますが、文字列をdoubleに解析する1つのユーティリティメソッドで問題が発生しています。変換にBigDecimalを使用すると問題が解決する修正を見つけましたが、BigDecimalをdoubleに変換し直すと、別の問題が発生します。精度がいくつか失われます。例えば:

import Java.math.BigDecimal;
import Java.text.DecimalFormat;

public class test {
    public static void main(String [] args){
        String num = "299792.457999999984";
        BigDecimal val = new BigDecimal(num);
        System.out.println("big decimal: " + val.toString());
        DecimalFormat nf = new DecimalFormat("#.0000000000");
        System.out.println("double: "+val.doubleValue());
        System.out.println("double formatted: "+nf.format(val.doubleValue()));
    }
}

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

$ Java test
big decimal: 299792.457999999984
double: 299792.458
double formatted: 299792.4580000000

フォーマットされたdoubleは、3位以降に精度が失われたことを示しています(アプリケーションでは、これらの低い精度の場所が必要です)。

BigDecimalを取得して、これらの追加の精度の場所を保持するにはどうすればよいですか?

ありがとう!


この投稿に追いついた後に更新します。これはdoubleデータ型の精度を超えていると言う人もいます。このリファレンスを間違って読んでいない限り: http://Java.Sun.com/docs/books/jls/third_edition/html/typesValues.html#4.2. ダブルプリミティブには最大値がありますEの指数値最大 = 2K-1-1であり、標準実装にはK = 11があります。つまり、最大指数は511である必要がありますね。

12

その数でdoubleの最大精度に達しました。それはできません。この場合、値は切り上げられます。 BigDecimalからの変換は無関係であり、精度の問題はどちらの方法でも同じです。たとえば、これを参照してください。

System.out.println(Double.parseDouble("299792.4579999984"));
System.out.println(Double.parseDouble("299792.45799999984"));
System.out.println(Double.parseDouble("299792.457999999984"));

出力は次のとおりです。

299792.4579999984
299792.45799999987
299792.458

このような場合、doubleの精度は小数点以下3桁を超えます。それらはたまたまあなたの数のゼロであり、それはあなたがdoubleに収めることができる最も近い表現です。この場合、切り上げに近いので、9が消えているように見えます。これを試してみると:

System.out.println(Double.parseDouble("299792.457999999924"));

切り捨てに近かったため、9が維持されていることがわかります。

299792.4579999999

番号のallの数字をすべて保持する必要がある場合は、doubleで動作するコードを変更する必要があります。それらの代わりにBigDecimalを使用できます。パフォーマンスが必要な場合は、オプションとして [〜#〜] bcd [〜#〜] を検討することをお勧めしますが、私は手元にあるライブラリを認識していません。


更新に応じて:倍精度浮動小数点数の最大指数は実際には1023です。ただし、これはここでの制限要因ではありません。あなたの数は、仮数を表す52の小数ビットの精度を超えています。 IEEE 754-1985 を参照してください。

この浮動小数点変換 を使用して、数値を2進数で表示します。 262144(2 ^ 18)が最も近いため、指数は18です。小数ビットを取得して2進数で上下に移動すると、数値を表すのに十分な精度がないことがわかります。

299792.457999999900 // 0010010011000100000111010100111111011111001110110101
299792.457999999984 // here's your number that doesn't fit into a double
299792.458000000000 // 0010010011000100000111010100111111011111001110110110
299792.458000000040 // 0010010011000100000111010100111111011111001110110111
21
WhiteFang34

問題は、doubleが15桁を保持できるのに対し、BigDecimalは任意の数を保持できることです。 toDouble()を呼び出すと、丸めモードを適用して余分な桁を削除しようとします。ただし、出力には9が多数含まれているため、0に切り上げられ続け、次に高い桁に桁上げされます。

できるだけ高い精度を維持するには、BigDecimalの丸めモードを変更して切り捨てる必要があります。

BigDecimal bd1 = new BigDecimal("12345.1234599999998");
System.out.println(bd1.doubleValue());

BigDecimal bd2 = new BigDecimal("12345.1234599999998", new MathContext(15, RoundingMode.FLOOR));
System.out.println(bd2.doubleValue());
3
Anon

その数の桁のみが出力されるため、文字列を解析してdoubleに戻すと、まったく同じ値になります。

詳細については、javadoc for Double#toString を参照してください。

Mまたはaの小数部分に何桁印刷する必要がありますか?小数部分を表すには少なくとも1桁が必要であり、それを超えると、引数値をdouble型の隣接する値と一意に区別するために必要な桁数だけになります。つまり、xが、有限の非ゼロ引数dに対してこのメ​​ソッドによって生成された10進表現によって表される正確な数学値であると仮定します。その場合、dはxに最も近いdouble値でなければなりません。または、2つのdouble値がxに等しく近い場合、dはそれらの1つであり、dの仮数の最下位ビットは0である必要があります。

2
Jörn Horstmann

完全にdoubleに基づいている場合...なぜBigDecimalを使用しているのですか? Doubleはもっと意味がありませんか?そのために値が大きすぎる(または精度が高すぎる)場合は...変換できません。それが、そもそもBigDecimalを使用する理由です。

精度が低下している理由については、 javadoc

このBigDecimalをdoubleに変換します。この変換は、Java言語仕様で定義されているdoubleからfloatへのナローイングプリミティブ変換に似ています。このBigDecimalの大きさが大きすぎてdoubleとして表されない場合、Doubleに変換されます。必要に応じて、NEGATIVE_INFINITYまたはDouble.POSITIVE_INFINITY。戻り値が有限の場合でも、この変換によってBigDecimal値の精度に関する情報が失われる可能性があることに注意してください。

0
Brian Roach

ダブルの可能な最大精度に達しました。それでも値をプリミティブに格納したい場合... 1つの可能な方法は、小数点の前の部分をlongに格納することです。

long l = 299792;
double d = 0.457999999984;

小数部を格納するための精度を使い果たしていないため(これは単語の選択としては不適切です)、小数部の精度をより多く保持できます。これは、いくつかの丸めなどで行うのに十分簡単なはずです。

0
Java Drinker