一、问题的提出:
如果我们编译运行下面这个程序会看到什么?
1 2 3 4 5 6 7 8
| public class Test{ public static void main(String args[]){ System.out.println(0.05+0.01); System.out.println(1.0-0.42); System.out.println(4.015*100); System.out.println(123.3/100); } };
|
你没有看错!结果确实是
1 2 3 4
| 0.060000000000000005 0.5800000000000001 401.49999999999994 1.2329999999999999
|
Java中的简单浮点数类型float和double不能够进行运算。不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,但是多试几次(可以做一个循环)就可以试出类似上面的错误。现在终于理解为什么要有BCD码了。
这个问题相当严重,如果你有9.999999999999元,你的计算机是不会认为你可以购买10元的商品的。
在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有。现在让我们看看如何解决这个问题。
四舍五入
我们的第一个反应是做四舍五入。Math类中的round方法不能设置保留几位小数,我们只能象这样(保留两位):
1 2 3
| public double round(double value){ return Math.round(value*100)/100.0; }
|
非常不幸,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,如我们在上面看到的
4.015*100=401.49999999999994
因此如果我们要做到精确的四舍五入,不能利用简单类型做任何运算
1 2
| java.text.DecimalFormat也不能解决这个问题: System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
|
输出是4.02
二、精确计算
在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal
我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》一书中的例子是用String来够造BigDecimal的
下面提供计算的代码:
(注意:divide方法中推荐使用枚举RoundingMode.HALF_UP)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| package com.wetalk.wbs.bas.util;
import java.io.Serializable; import java.math.BigDecimal; import java.math.RoundingMode;
public class DoubleUtil implements Serializable { private static final long serialVersionUID = -3345205828566485102L; private static final Integer DEF_DIV_SCALE = 2;
public static Double add(Double value1, Double value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1)); BigDecimal b2 = new BigDecimal(Double.toString(value2)); return b1.add(b2).doubleValue(); }
public static double sub(Double value1, Double value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1)); BigDecimal b2 = new BigDecimal(Double.toString(value2)); return b1.subtract(b2).doubleValue(); }
public static Double mul(Double value1, Double value2) { BigDecimal b1 = new BigDecimal(Double.toString(value1)); BigDecimal b2 = new BigDecimal(Double.toString(value2)); return b1.multiply(b2).doubleValue(); }
public static Double divide(Double dividend, Double divisor) { return divide(dividend, divisor, DEF_DIV_SCALE); }
public static Double divide(Double dividend, Double divisor, Integer scale) { if (scale < 0) { throw new IllegalArgumentException("The scale must be a positive integer or zero"); } BigDecimal b1 = new BigDecimal(Double.toString(dividend)); BigDecimal b2 = new BigDecimal(Double.toString(divisor)); return b1.divide(b2, scale,RoundingMode.HALF_UP).doubleValue(); }
public static double round(double value,int scale){ if(scale<0){ throw new IllegalArgumentException("The scale must be a positive integer or zero"); } BigDecimal b = new BigDecimal(Double.toString(value)); BigDecimal one = new BigDecimal("1"); return b.divide(one,scale, RoundingMode.HALF_UP).doubleValue(); } }
|