7

次のコードを試しました。ただし、BigDecimalを使用して減算すると、異なる結果が得られます。

    double d1 = 0.1;
    double d2 = 0.1;
    System.out.println("double result: "+ (d2-d1));

    float f1 = 0.1F;
    float f2 = 0.1F;
    System.out.println("float result: "+ (f2-f1));

    BigDecimal b1 = new BigDecimal(0.01);
    BigDecimal b2 = new BigDecimal(0.01);

    b1 = b1.subtract(b2);
    System.out.println("BigDecimal result: "+ b1);

結果:

double result: 0.0
float result: 0.0
BigDecimal result: 0E-59

私はまだこれに取り組んでいます。誰でも明確にしてください。

4

8 に答える 8

11

[ここには、バイナリ浮動小数点が0.01を正確に表すことができないことを示す多くの回答があり、表示されている結果が何らかの形で不正確であることを意味します。その最初の部分は真実ですが、それは実際にはここでの中心的な問題ではありません。]


答えは、「0E-59」0に等しいということBigDecimalです。aは、スケーリングされていない値と10進数のスケール係数の組み合わせであることを思い出してください。

System.out.println(b1.unscaledValue());
System.out.println(b1.scale());

表示:

0
59

予想どおり、スケーリングされていない値0です。「奇妙な」スケール値は、0.01の非正確な浮動小数点表現の10進展開のアーティファクトにすぎません。

System.out.println(b2.unscaledValue());
System.out.println(b2.scale());

表示:

1000000000000000020816681711721685132943093776702880859375
59

次の明らかな質問は、便宜上、なぜ「 」とBigDecimal.toStringだけ表示されないのかということです。答えは、文字列表現が明確である必要があるということです。Javadocから:b10toString

BigDecimal識別可能な値とこの変換の結果の間には、1対1のマッピングがあります。つまり、すべての識別可能なBigDecimal値(スケーリングされていない値とスケール)は、を使用した結果として一意の文字列表現を持ちますtoString。その文字列表現がコンストラクターBigDecimalを使用してに変換されたBigDecimal(String)場合、元の値が復元されます。

「」と表示されただけでは、この正確なオブジェクト0に戻ることはできません。BigDecimal

于 2013-03-15T09:35:24.293 に答える
7

Stringのコンストラクターを使用します。b1 = new BigDecimal("0.01");

Javaの精度の低下

(スライド23) http://strangeloop2010.com/system/talks/presentations/000/014/450/BlochLee-JavaPuzzlers.pdf

于 2013-03-15T09:24:37.727 に答える
4

興味深いことに、値は等しいように見え、減算するとゼロになります。これは、印刷コードの問題であるように見えます。次のコード:

import java.math.BigDecimal;
public class Test {
    public static void main(String args[]) {
        BigDecimal b1 = new BigDecimal(0.01);
        BigDecimal b2 = new BigDecimal(0.01);
        BigDecimal b3 = new BigDecimal(0);
        if (b1.compareTo(b2) == 0) System.out.println("equal 1");
        b1 = b1.subtract(b2);
        if (b1.compareTo(b3) == 0) System.out.println("equal 2");
        System.out.println("BigDecimal result: "+ b1);
    }                          
}

両方の equalメッセージを出力し、値同じであり、減算するとゼロになることを示します。

これをバグとして取り上げて、Oracleが何を返すかを確認することができます。0e-59バグではなく、まだゼロであるとか、BigDecimalのドキュメントページで説明されているかなり複雑な動作が意図したとおりに機能しているとだけ表示される可能性があります。具体的には、次のように述べています。

識別可能なBigDecimal値とこの変換の結果の間には1対1のマッピングがあります。つまり、すべての識別可能なBigDecimal値(スケールされていない値とスケール)は、toStringを使用した結果として一意の文字列表現を持ちます。その文字列表現がBigDecimal(String)コンストラクターを使用してBigDecimalに変換された場合、元の値が復元されます。

元の値が回復可能である必要があるという事実は、toString()スケールごとに一意の文字列を生成する必要があることを意味します。これが、を取得する理由です0e-59。そうしないと、文字列をaに戻すBigDecimalと、異なる値(unscaled-value / scale tuple)が得られる場合があります。

スケールに関係なく、本当にゼロを「0」として表示したい場合は、次のように使用できます。

if (b1.compareTo(BigDecimal.ZERO) == 0) b1 = new BigDecimal(0);
于 2013-03-15T09:23:18.970 に答える
2

BigDecimal(double val)

1.このコンストラクターの結果は、多少予測できない場合があります。Javaで新しいBigDecimal(0.1)を書き込むと、0.1に正確に等しいBigDecimal(スケールなしの値1、スケール1)が作成されると思われるかもしれませんが、実際には0.1000000000000000055511151231257827021181583404541015625に等しくなります。これは、0.1を正確にdoubleとして(または、さらに言えば、任意の有限長の2進数として)表すことができないためです。したがって、コンストラクターに渡される値は、外観にもかかわらず、0.1と正確に等しくはありません。

2.一方、Stringコンストラクターは完全に予測可能です。新しいBigDecimal( "0.1")を作成すると、予想どおり、0.1に正確に等しいBigDecimalが作成されます。したがって、一般的には、これよりもStringコンストラクターを使用することをお勧めします。

3. DoubleをBigDecimalのソースとして使用する必要がある場合、このコンストラクターは正確な変換を提供することに注意してください。Double.toString(double)メソッドを使用してdoubleをStringに変換してから、BigDecimal(String)コンストラクターを使用した場合と同じ結果は得られません。その結果を得るには、静的なvalueOf(double)メソッドを使用します。

于 2013-03-15T09:31:48.443 に答える
2

戻り値を取得する必要があります。

BigDecimal b3 = b1.subtract(b2);
System.out.println("BigDecimal result: "+ b3);
于 2013-03-15T09:23:51.220 に答える
2

したがって、本当の問題は次のとおりです。次のコードを使用すると、

BigDecimal b1 = new BigDecimal(0.01);
BigDecimal b2 = new BigDecimal(0.01);
b1 = b1.subtract(b2);

なぜ、または単にのようなものに評価され、b1.toString()評価されないのですか?"0E-59""0.0""0E0""0"


その理由は、の標準形式をtoString()印刷するためです。詳細については、 BigDecimal.toString()を参照してください。BigDecimal

最後に、0E-59 0.0-0*10^59数学的に0と評価されるものです。したがって、予期しない結果は、の内部表現の問題ですBigDecimal

floatまたはdouble値を取得するには、次を使用します。

b1.floatValue());

また

b1.doubleValue());

両方ともに評価され0.0ます。

于 2013-03-15T09:26:37.897 に答える
1

これは既知の問題であり、BigDecimal(double val)APIです。このコンストラクターの結果は、多少予測できない場合があります。この解釈では本当に奇妙に見えますが。実際の理由は、新しいBigDecimal(0.01)がおおよその値を持つBigDecimalを生成することです。

0.01000000000000000020816681711721685132943093776702880859375

これは精度が長いため、減算の結果も精度が長くなります。

とにかく、この方法で「問題」を解決することができます

BigDecimal b1 = new BigDecimal("0.01");
BigDecimal b2 = new BigDecimal("0.01");

または、精度を設定してコンストラクターを使用できます

BigDecimal b1 = new BigDecimal(0.01, new MathContext(1));
BigDecimal b2 = new BigDecimal(0.01, new MathContext(1));
于 2013-03-15T09:28:12.217 に答える
1

このように使用します:

BigDecimal b1 = BigDecimal.valueOf(0.01);
BigDecimal b2 = BigDecimal.valueOf(0.01);

b1 = b1.subtract(b2);
System.out.println("BigDecimal result: "+ b1);
于 2016-10-20T07:08:10.290 に答える