第48条:如果要精确的结果,请避免使用float和double

float和double类型主要是为了科学计算和工程计算而设计的。她们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结构,所以不应该被用于需要精确结果的场合。float和double类型尤其不适用于货币计算,因为要让一个float或者double精确地表示0.1(或者10的任何其它负数次方值)是不可能的。

例子:你现在有1元钱

货架上有一排糖果,标价为: 10分、20分、30分、40分、50分、60分…

你要从标价为10分的糖果开始,每种买1颗,一直到不能支付货架上下一种价格的糖果为止。傻子都看得出能买到4种糖果^_^(10+20+30+40),但是编程计算处理不当就会出问题!

错误的示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public static void main(String[] args) {

double funds = 1.00;

int itemBought = 0;

for(double price = 0.10; funds >= price; price += 0.10) {

funds -= price;

itemBought++;

}

System.out.println("能买到" + itemBought + "种糖果!");

System.out.println("剩余金额 ¥" + funds);

}

运行上面这个程序你会发现你只能买到3中糖果,还剩余0.399999… GG! 出现这种情况的原因是float的计算精度造成的~

解决方案

1.使用BigDecimal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public static void main(String[] args) {

final BigDecimal TEN_CENTS = new BigDecimal("0.10");
int itemBought = 0;

BigDecimal funds = new BigDecimal("1.00");

for(BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
itemBought++;

funds = funds.substract(price);

}
System.out.println("能买到" + itemBought + "种糖果!");
System.out.println("剩余金额 ¥" + funds);
}

2.使用int或long

除了使用BigDecimal之外,还有一种方法是使用int或者long,到底使用int或者龙要取决于所涉及数值的大小,同时要自己处理十进制小数点。在这个例子中,最明显的做法是以分为单位进行计算,而不是以元为单位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public static void main(String[] args) {

int itemBought = 0;

int funds = 100;

for( int price = 10; funds >= price; price += 10) {

itemBought++;

funds -= price;

}

System. out.println( "能买到" + itemBought + "种糖果!" );

System. out.println( "剩余金额 ¥" + funds);

}

总结

  • 对于需要精确答案的计算任务,不要使用float或者double

  • 数值范围没有超过9位十进制数字,就可以使用int,如果不超过18位数字,就可以使用long,如果数值范围可能超过18位数字,就要使用BigDecimal

  • 使用BigDecimal有两个缺点,与使用基本运算类型相比,这样子很不方便,而且很慢。但是计算结果精确,允许你完全控制舍入(8种舍入)