float和double主要为了科学计算和工程计算而设计,执行二进制浮点运算,为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不适合用于需要精确结果的场合,尤其是货币计算。因为两位小数的话,相减,很可能就会出现问题,例如有1.03元,花出去0.42元,
System.out.println(1.03 - .42);
结果为 0.6100000000000001
System.out.println(1.00 - 9 * .10);
结果为 0.09999999999999998
这就是案例,没办法获取精确的值,如果四舍五入的话,也基本能满足基本的计算,稍微要求高一点的就不行了,比如,有0.1,0.2,0.3,一直到1元的糖果,我们有1块钱,从便宜的开始买,能买几块糖果呢?
static void test() {
double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10; funds >= price; price += 0.10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}
打印结果是
3 items bought.
Money left over: $0.3999999999999999
意思是买了3块,剩余 0.3999999999999999 元;这个明显有问题,明眼人都知道,可以买4块糖,钱正好花完。精确的答案还是需要 BigDecimal 这个类,换一下
static void test1() {
final BigDecimal TEN_CENTS = new BigDecimal(".10");
int itemsBought = 0;
BigDecimal funds = new BigDecimal("1.00");
for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
itemsBought++;
funds = funds.subtract(price);
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}
打印结果
4 items bought.
Money left over: $0.00
这一次就刚刚好了。看来,如果要获取精确的结构,可以使用 BigDecimal 。既然这个类能获取精确答案,为什么不用它代替float和double呢?这就需要提及它的两个缺点了:与基本类型相比,它需要创建一个 BigDecimal 对象;速度问题,比着基本类型,速度较慢。所以,平时还是尽量使用基本类型,如果用到了小数,我们可以先想办法转换,小数转换成整数来运算,比如上述的1块钱买糖的问题,单位是元,如果我们把单位用分来代替,则是100分买糖果,糖果的价格是 10 、 20 、 30 依次上涨,这样,我们写函数就可以用整数了,
static void test2() {
int itemsBought = 0;
int funds = 100;
for (int price = 10; funds >= price; price += 10) {
itemsBought++;
funds -= price;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}
打印结果
4 items bought.
Money left over: $0
碰到问题转换思路,如果实在转换不了,并且还必须是精确类型,那么就使用 BigDecimal 。使用BigDecimal还有一些额外的好处,他允许你完全控制舍入,每当一个操作涉及舍入的时候,他允许你从8种舍入模式中选择其一,这个比较方便。具体使用哪个,还是需要具体业务来判断。