关于java:不可预知的双倍

Unpredictable double

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Double precision problems on .NET
Double calculation producing odd result

我知道,双值0.2的内部再销售类似于0.199999。但是下面的代码仍然让我困惑。

代码:

1
2
3
4
5
6
7
8
9
10
11
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));
}

输出:

1
2
3
4
5
6
7
0.4
0.03
0.3
0.1
0.19999999999999998
2.9999999999999996
0.039999999999999994

实际上发生了什么?加法,乘法很好,但减法,除法不行。有人能解释一下为什么加法和减法不同吗?


如果你渴望精确,就用bigdecimal。

1
2
3
4
5
6
7
8
9
10
11
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)));
}

产量

1
2
3
4
5
6
7
0.4
0.03
0.3
0.1
0.2
3
0.04

或者舍入您的结果,decimalFormat可以很好地做到这一点,使用符号意味着仅在必要时显示小数。

1
2
3
4
5
6
7
8
9
10
    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)));

产量

1
2
3
4
5
6
7
0.4
0.03
0.3
0.1
0.2
3
0.04

简而言之,对于浮点运算,您有一个表示错误和一个舍入错误。toString()知道表示错误,所以如果没有舍入错误,就看不到。但是,如果舍入误差太大,就需要这样做。

解决方案是要么使用bigdecimal,要么对结果取整。

如果您使用bigdecimal,它将显示您真正拥有的精确值。

1
2
3
4
5
6
7
8
9
10
11
12
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)));

印刷品

1
2
3
4
5
6
7
8
9
10
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

您会注意到0.1稍微太大,0.3稍微太小。这意味着当你加上或乘上它们时,你会得到一个正确的数字。但是,如果使用减法或除法,错误会累积,得到的数字与表示的数字相差太远。

也就是说,0.1和0.3的结果与0.4的结果相同,而0.3-0.1的结果与0.2的结果不同

btw在不使用bigdecimal的情况下对答案进行四舍五入,您可以使用

1
2
3
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));

印刷品

1
2
3
d-f= 0.20
d/f= 3.00
(d-f)*(d-f)= 0.04

1
2
3
4
5
6
7
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;
}

印刷品

1
2
3
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)));

舍入消除舍入误差(只留下ToString设计用来处理的表示误差)

在0.1之前和之后可以表示的值可以计算为

1
2
3
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)));

印刷品

1
2
3
4
The value before 0.1 is 0.09999999999999999167332731531132594682276248931884765625
    error= 8.32667268468867405317723751068115234375E-18
The value after 0.1 is  0.1000000000000000055511151231257827021181583404541015625
    error= 5.5511151231257827021181583404541015625E-18