BigDecimal实战与分析
一.BigDecimal的坑
首先,我们来看一个BigDecimal的实例:
package com.bijian.test; import java.math.BigDecimal; public class Test01 { public static void main(String[] args) { BigDecimal b1 = new BigDecimal("10.00"); BigDecimal b2 = new BigDecimal("10"); System.out.println(b1.equals(b2)); System.out.println(b1.compareTo(b2)); System.out.println(b1.toPlainString()); System.out.println(b2.toPlainString()); } }
运行结果:
false
0
10.00
10
为什么都是10呀,怎么equals为false?通过toPlainString()方法也能明白,他们构造方法传入的字符串不同,而它们表示的含义都是10,所以compareTo却是0。
再来看一实例:
package com.bijian.test; import java.math.BigDecimal; public class Test { public static void main(String[] args) { BigDecimal b1 = new BigDecimal("0.01"); BigDecimal b2 = new BigDecimal(0.01); System.out.println(b1.equals(b2)); System.out.println(b1.toPlainString()); System.out.println(b2.toPlainString()); } }
运行结果:
false
0.01
0.01000000000000000020816681711721685132943093776702880859375
这下还真是疑惑了,为什么变这么长了一串数字了呢?原来JDK是这样描述的。
JDK的描述:
1.参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
2.另一方面,String 构造方法是完全可预知的:写入 newBigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。
3.当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。
进一步查资料得知:原因在于我们的计算机是二进制的。浮点数没有办法是用二进制进行精确表示。我们的CPU表示浮点数由两个部分组成:指数和尾数,这样的表示方法一般都会失去一定的精确度,有些浮点数运算也会产生一定的误差。如:2.4的二进制表示并非就是精确的2.4。反而最为接近的二进制表示是 2.3999999999999999。浮点数的值实际上是由一个特定的数学公式计算得到的。
二.BigDecimal加法操作
package com.bijian.test; import java.math.BigDecimal; public class Test { public static void main(String[] args) { BigDecimal a =new BigDecimal("1.22"); BigDecimal b =new BigDecimal("2.22"); a.add(b); System.out.println("a add b is : " + a); } }
应该有人会认为结果是:a add b is : 3.44,但实际运行的结果是:a add b is : 1.22。
原因是:BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。
修改代码如下,执行结果将是:a add b is : 3.44。
package com.bijian.test; import java.math.BigDecimal; public class Test { public static void main(String[] args) { BigDecimal a =new BigDecimal("1.22"); BigDecimal b =new BigDecimal("2.22"); a = a.add(b); System.out.println("a add b is : " + a); } }
三.BigDecimal的3个toString方法
BigDecimal类有3个toString方法,分别是toEngineeringString、toPlainString和toString,从BigDecimal的注释中可以看到这3个方法的区别:
1.toEngineeringString:有必要时使用工程计数法。工程记数法是一种工程计算中经常使用的记录数字的方法,与科学技术法类似,但要求10的幂必须是3的倍数。(Returns a string representation of this BigDecimal, using engineering notation if an exponent is needed.)
2.toPlainString:不使用任何指数(Returns a string representation of this BigDecimal without an exponent field)。
3.toString:有必要时使用科学计数法(Returns the string representation of this BigDecimal, using scientific notation if an exponent is needed.)

看看如下程序示例:
package com.bijian.test; import java.math.BigDecimal; public class Test { public static void main(String[] args) { BigDecimal bg = new BigDecimal("1E11"); System.out.println(bg.toEngineeringString()); System.out.println(bg.toPlainString()); System.out.println(bg.toString()); } }
运行结果:
100E+9
100000000000
1E+11
四.进一步了解BigDecimal
借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。
BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负scale 次幂。因此,BigDecimal表示的数值是(unscaledValue × 10-scale)。
五.BigDecimal小结
1.商业计算使用BigDecimal。
2.尽量使用参数类型为String的构造函数。
3.BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。
4.我们往往容易忽略JDK底层的一些实现细节,导致出现错误,需要多加注意。
posted on 2017-06-08 00:54 bijian1013 阅读(228) 评论(0) 收藏 举报
浙公网安备 33010602011771号