JAVA解惑--找零时刻

   请考虑下面这段话描述的问题:
   有人在一家汽车配件商店购买了一个价值$1.10的火花塞,但是他只有两美元一张的钞票。如果他用一张两美元的钞票支付这个火花塞,那么应该找给他多少零钱呢?
   下面是一个试图解决上述问题的程序,它会打印出什么呢?

public class Change{
   public static void main(String args[]){
      System.out.println(2.00-1.10);
   }
}

   你可能会很天真的期望该程序能够打印出0.90,但是它如何才能知道你要打印小数点后两位小数呢?
   如果你对在Double.toString文档中所设定的将double类型的值转换为字符串的规则有所了解,你就会知道该程序打印出来的小数,足以将double类型的值与最靠近它的临界值区分出来的最短的小数,它在小数点之前和之后都至少有一位,因此看起来,该程序应该打印0.90是合理的。
   这么分析可能显得很合理,但是并不正确。如果你运行该程序,你就会发现它打印的是0.8999999999999999。
   问题在于1.1这个数字不能被准确的表示成为一个double,因此它被表示成为最接近它的double值。该程序就是从2中减去这个值。遗憾的是,这个计算的结果并不是最接近0.9的double值。表示结果的double值的最短表示就是你所看到的打印出来的那个数字。
   更一般的说,问题在于并不是所有的小数都可以用二进制浮点数来精确表示。
   如果你使用的是JDK5.0或更新的版本,可以通过printf工具来设置输出精度的方法来修改     该程序:

System.out.printf("%.2f%n",2.00-1.10);

   这条语句打印的是正确结果,但是这并不代表它就是对底层问题的通用解决方法:它使用的仍旧是二进制浮点数的double运算。浮点数在一个范围很广的值域上提供了很好的近似,但是它同城不能产生精确的结果。二进制浮点对于货币计算是非常不适合的,因为它不可能将0.1精确的表示为一个长度有限的二进制小数。
   解决该问题的一种方式是使用某种整数类型,例如int或long,并且以分为单位来执行计算,修改后程序如下:

System.out.println((200-100) + "cents");

   解决该问题的另一种方式是使用执行精确小数运算的BigDecimal。它还可以通过JDBC与SQL DECIMAL类型进行互操作。这里要告诫读者一点,一定要用BigDecimal(String)构造器,而不要使用BigDecimal(double)。后一个构造器江勇它的参数的“精确值”来创建一个实例:new BigDecimal(.1)将返回一个表示0.100000000000000055511151231257827021181583404541015625的BigDecimal。通过使用BigDecimal就可以打印出我们想要的结果0.90:
import java.math.BigDecimal;
public class Change{
   public static void main(String[] args){
      System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
   ]
}

   这个版本并不是十分的完美,因为Java并没有为BigDecimal提供任何语言上的支持。使用BigDecimal的计算很可能比那些使用原始类型的计算要慢一些。对于某些大量使用小数的程序来说这是个问题,而对于大多数程序来说,这一点显得也不重要。
总之在需要精确答案的地方,要避免使用float和double,对于货币计算,要使用int、long或BigDecimal。对于语言设计者来说,应该考虑对小数运算提供语言支持。

猜你喜欢

转载自xiaofuyan.iteye.com/blog/2354406