1

重複の可能性:
.NETの倍精度の問題
二重計算により奇妙な結果が生成される

double値の内部表現はの0.2ようなものであることを私は知ってい0.199999ます。しかし、次のコードはまだ私を混乱させます。

コード:

public static void main(String[] args) {
    double d= 0.3d;
    double f= 0.1d;
    System.out.println(d+f);
    System.out.println(d*f);
    System.out.println(d);
    System.out.println(f);
    System.out.println(d-f);
    System.out.println(d/f);
    System.out.println((d-f)*(d-f));
}

出力:

0.4
0.03
0.3
0.1
0.19999999999999998
2.9999999999999996
0.039999999999999994

実際に何が起こっているのですか?足し算、掛け算はうまくいきますが、引き算、割り算はうまくいきません。足し算と引き算が違う理由を誰かが詳しく説明してもらえますか?

4

2 に答える 2

5

精度が必要な場合は、BigDecimalを使用してください。

public static void main(String[] args) {
    BigDecimal d = BigDecimal.valueOf(0.3d);
    BigDecimal f = BigDecimal.valueOf(0.1d);
    System.out.println(d.add(f));
    System.out.println(d.multiply(f));
    System.out.println(d);
    System.out.println(f);
    System.out.println(d.subtract(f));
    System.out.println(d.divide(f));
    System.out.println((d.subtract(f)).multiply(d.subtract(f)));
}

出力

0.4
0.03
0.3
0.1
0.2
3
0.04

または、結果を丸めると、DecimalFormatは、必要な場合にのみ小数を表示することを意味する#記号を使用してこれを非常にうまく実行します

    double d = 0.3d;
    double f = 0.1d;
    DecimalFormat format = new DecimalFormat("#.##");
    System.out.println(format.format(d + f));
    System.out.println(format.format(d * f));
    System.out.println(format.format(d));
    System.out.println(format.format(f));
    System.out.println(format.format(d - f));
    System.out.println(format.format(d / f));
    System.out.println(format.format((d - f) * (d - f)));

出力

0.4
0.03
0.3
0.1
0.2
3
0.04
于 2012-03-12T12:57:09.810 に答える
4

The short answer is you have a representation error and a rounding error for floating point operations. The toString() "knows" about the representation error so if there is no rounding error you don't see it. But if the rounding error is too large, you do.

The solution is to either use BigDecimal or round your result.


If you use BigDecimal it will show the exact values you really have.

double d = 0.3d;
double f = 0.1d;
System.out.println("d= " + new BigDecimal(d));
System.out.println("f= " + new BigDecimal(f));
System.out.println("d+f= " + new BigDecimal(d + f));
System.out.println("0.4= " + new BigDecimal(0.4));
System.out.println("d*f= " + new BigDecimal(d * f));
System.out.println("0.03= " + new BigDecimal(0.03));
System.out.println("d-f= " + new BigDecimal(d - f));
System.out.println("0.2= " + new BigDecimal(0.2));
System.out.println("d/f= " + new BigDecimal(d / f));
System.out.println("(d-f)*(d-f)= " + new BigDecimal((d - f) * (d - f)));

prints

d= 0.299999999999999988897769753748434595763683319091796875
f= 0.1000000000000000055511151231257827021181583404541015625
d+f= 0.40000000000000002220446049250313080847263336181640625
0.4= 0.40000000000000002220446049250313080847263336181640625
d*f= 0.0299999999999999988897769753748434595763683319091796875
0.03= 0.0299999999999999988897769753748434595763683319091796875
d-f= 0.1999999999999999833466546306226518936455249786376953125
0.2= 0.200000000000000011102230246251565404236316680908203125
d/f= 2.999999999999999555910790149937383830547332763671875
(d-f)*(d-f)= 0.03999999999999999389377336456163902767002582550048828125

You will notice that 0.1 is slightly too large and 0.3 is slightly too small. This means that when you add or multiply them you get a number which is about right. However if you use subtract or division, the errors accumulate and you get a number which is too far from the represented number.

i.e. you can see that 0.1 and 0.3 results in the same value as 0.4, whereas 0.3 - 0.1 doesn't result in the same value as 0.2


BTW to round the answer without using BigDecimal you can use

System.out.printf("d-f= %.2f%n", d - f);
System.out.printf("d/f= %.2f%n", d / f);
System.out.printf("(d-f)*(d-f)= %.2f%n", (d - f) * (d - f));

prints

d-f= 0.20
d/f= 3.00
(d-f)*(d-f)= 0.04

or

System.out.println("d-f= " +  roundTo6Places(d - f));
System.out.println("d/f= " +  roundTo6Places(d / f));
System.out.println("(d-f)*(d-f)= " +  roundTo6Places((d - f) * (d - f)));

public static double roundTo6Places(double d) {
    return (long)(d * 1e6 + (d > 0 ? 0.5 : -0.5)) / 1e6;
}

prints

System.out.println("d-f= " +  roundTo6Places(d - f));
System.out.println("d/f= " +  roundTo6Places(d / f));
System.out.println("(d-f)*(d-f)= " +  roundTo6Places((d - f) * (d - f)));

The rounding removes the rounding error (leaving only the representation error which the toString is designed to handle)


The value which can be represented before and after 0.1 can be calculated as

double before_f = Double.longBitsToDouble(Double.doubleToLongBits(f) - 1);
System.out.println("The value before 0.1 is " + new BigDecimal(before_f) + " error= " + BigDecimal.valueOf(0.1).subtract(new BigDecimal(before_f)));
System.out.println("The value after 0.1 is  " + new BigDecimal(f) + " error= " + new BigDecimal(f).subtract(BigDecimal.valueOf(0.1)));

prints

The value before 0.1 is 0.09999999999999999167332731531132594682276248931884765625 
    error= 8.32667268468867405317723751068115234375E-18
The value after 0.1 is  0.1000000000000000055511151231257827021181583404541015625 
    error= 5.5511151231257827021181583404541015625E-18
于 2012-03-12T12:53:01.703 に答える