不可预知的双重

可能重复:
.NET上的双精度问题
双重计算产生奇数结果

我知道双值0.2内部represantation类似于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(df); System.out.println(d/f); System.out.println((df)*(df)); } 

OUTPUT:

 0.4 0.03 0.3 0.1 0.19999999999999998 2.9999999999999996 0.039999999999999994 

实际上发生了什么? 加法,乘法顺利,但减法,除法不是。 任何人都可以详细说明为什么加法与减法不同

简短的回答是浮点运算有一个表示错误和舍入错误。 toString() “知道”表示错误,所以如果没有舍入错误,你就看不到它。 但是如果舍入误差太大,那么你就做了。

解决方案是使用BigDecimal或舍入您的结果。


如果你使用BigDecimal,它将显示你真正拥有的确切值。

 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("df= " + 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("(df)*(df)= " + new BigDecimal((d - f) * (d - f))); 

版画

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

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

即,您可以看到0.1和0.3的结果与0.4相同,而0.3 – 0.1的结果与0.2的结果不同


顺便说一句,你可以使用BigDecimal来回答答案

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

版画

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

要么

 System.out.println("df= " + roundTo6Places(d - f)); System.out.println("d/f= " + roundTo6Places(d / f)); System.out.println("(df)*(df)= " + roundTo6Places((d - f) * (d - f))); public static double roundTo6Places(double d) { return (long)(d * 1e6 + (d > 0 ? 0.5 : -0.5)) / 1e6; } 

版画

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

舍入删除舍入错误(仅留下toString旨在处理的表示错误)


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

 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))); 

版画

 The value before 0.1 is 0.09999999999999999167332731531132594682276248931884765625 error= 8.32667268468867405317723751068115234375E-18 The value after 0.1 is 0.1000000000000000055511151231257827021181583404541015625 error= 5.5511151231257827021181583404541015625E-18 

如果你迫切需要精确使用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