float和double主要为了科学计算和工程计算而设计,执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不适合用于需要精确结果的场合,尤其是货币计算。
例如当前有1.03元,花掉0.42元后,应该还有0.61元。但是我们在使用float类型进行计算时,其结果是不准确的,如下所示:
System.out.println(1.03 - 0.42); //0.6100000000000001
使用舍入可以解决上面问题,但并不是所有的问题都能用舍入解决。例如当前总金额有1元,有0.1、0.2、0.3元的商品,从0.1开始买,直到不能支付为止:
public static void main(String[] args) {
double money = 1.00;
int total = 0;
for(double price = 0.1; money >= price; price += 0.1) {
money = money - price;
total++;
}
System.out.println("商品购买数:"+ total);
System.out.println("余额:" + money);
}
执行上述代码,其运行结果为:
商品购买数:3
余额:0.3999999999999999
使用BigDecimal代替double是可以解决上述问题的,但是出现了一个会忽略的意外,示例代码如下:
public static void main(String[] args) {
final BigDecimal increasePrice = new BigDecimal(0.1);
BigDecimal money = BigDecimal.valueOf(1.0);
int total = 0;
for (BigDecimal price = new BigDecimal("0.10"); money.compareTo(price) >= 0; price = price.add(increasePrice)) {
money = money.subtract(price);
total++;
}
System.out.println("商品购买数:" + total);
System.out.println("余额:" + money);
}
执行上述代码,其运行结果为:
商品购买数:3
余额:0.3999999999999999833466546306226518936455249786376953125
可以看到其运行结果和预期的不一样,这是由于使用BigDecimal的构造方法直接来转换float或者double类型时,会存在数据值不准确的情况。如果想要计算准确的话,可以使用采用下述两种方法来创建BigDecimal对象:构建参数为字符串或者使用valueOf方法:
final BigDecimal increasePrice = new BigDecimal("0.1");
final BigDecimal increasePrice = BigDecimal.valueOf(0.1);
public static void main(String[] args) {
final BigDecimal increasePrice = BigDecimal.valueOf(0.1);
BigDecimal money = new BigDecimal(1.0);
int total = 0;
for (BigDecimal price = new BigDecimal("0.10"); money.compareTo(price) >= 0; price = price.add(increasePrice)) {
money = money.subtract(price);
total++;
}
System.out.println("商品购买数:" + total);
System.out.println("余额:" + money);
}
再次执行上述代码,其运行结果为:
商品购买数:4
余额:0.00
但是使用BigDecimal有下述三个缺点:
- 与基本类型相比,不方便(需要创建BigDecimal对象);
- 速度慢
- 直接使用构造方法转换double或者float,会存在数据不准确的情况
除了使用BigDecimal,还可以使用int或者long,取决于涉及的数值大小,同时要自己处理十进制小数,例如上述案例中金额以分为单位,而不是以元为单位计算,就可以使用int来处理:
public static void main(String[] args) {
int money = 100;
int total = 0;
for (int price = 10; money >= price; price += 10) {
money = money - price;
total++;
}
System.out.println("商品购买数:" + total);
System.out.println("余额:" + money);
}
总结:
- 对于任何需要精确答案的计算任务,请不要使用float或者double。
- 如果你想让系统来记录十进制小数点,并且不介意因为不使用基本类型而带来的不便,就请使用BigDecimal。使用BigDecimal还有一些额外的好处,它允许你完全控制舍入,每当一个操作涉及舍入的时候,他允许你从8种舍入模式中选择其一。如果你正通过法定要求的舍入行为进行业务计算,使用BigDecimal是非常方便的。
- 如果性能非常关键,并且不介意自己记录十进制小数点,而且所涉及的数值又不太大,就可以使用int或者long。如果数值范围内没有超过9位十进制数字,就可以使用int;如果不超过18位数字,就可以使用long。如果数值可能超过18位数字,就必须使用BigDecimal。