java解惑之谜题2:找零时刻
谜题由下面这段话所描述的问题引出:
Tom在一家汽车配件商店购买一个价值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.9;但在程序运行后,打印出来的结果不是我们想要的,而是0.8999999999999999,不仅仅是上述打印的结果出乎我们的意料,而且在大部分的double和float类型做运算时,打印的结果也不是我们所期望的准确的值。产生该问题的问题在于并不是所有的小数都可以用二进制浮点数精确表示,而在货币计算方面,采用二进制浮点进行计算是非常不合适的,因为它不可能将0.1或者10的任何次负幂精确的表示为一个有限长度的二进制小数。浮点运算在一个范围很广的值域上提供了很好的近似,但是通常不能产生精确的结果,那么怎么来解决这个问题呢?
解决方法有三种:
1.在JDK 5.0或更高版本中,可以采用输出精度的方法,例如System.out.printf("%.2f%n",2.00-1.10),但这并不表示这个方法是对底层问题的通用解决方案,它使用的仍旧是二进制浮点数的double运算;
2.使用整型类型来进行货币计算,将货币单位化为最小单位或者适合整型类型计算的单位;
3.使用执行精确小数运算的BigDecimal,该类提供了多种不同的构造方法,其中有BigDecimal(String)构造器和BigDecimal(double)构造器,那么,从参数看,我们肯定会去使用BigDecimal(double)的构造器,接着运行后发现依旧得不到自己想要的值。其实,在API文档中对该构造器有很详细的说明,此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入new BigDecimal(0.1)所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于:0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。 另一方面,String 构造方法是完全可预知的:写入new BigDecimal("0.1") 将创建一个 BigDecimal,它正好 等于预期的 0.1。所以说我们一定要用BigDecimal(String)构造器,这通常是将float 或 double 转换为 BigDecimal 的首选方法,因为它不会遇到 BigDecimal(double) 构造方法的不可预知问题。
总之,在需要精确答案的地方,要避免使用float和double;对于货币计算,用使用int,long或BigDecimal。