您的位置:首页 > 编程语言 > Java开发

关于BigDecimal精度影响计算结果的问题

2017-01-03 20:55 411 查看
因为double本身有数值范围的限制,在处理金钱等需要高精度的数据时,我们会考虑使用BigDecimal。但是在使用BigDecimal时,有很多需要我们注意的细节。本文主要针对精度问题来进行说明。


设置和获取Scale精度

我们可能都知道BigDecimal是有精度的,通过以下几个setScale()重载方法,可以对精度进行设置。

 

BigDecimalsetScale(int newScale)Returns a BigDecimal whose scale is the specified value, and whose value is numerically equal
to this BigDecimal's.
BigDecimalsetScale(int newScale, int roundingMode)Returns a BigDecimal whose scale is the specified value, and whose
unscaled value is determined by multiplying or dividing this BigDecimal's unscaled value by the appropriate power of ten to maintain its overall value.
BigDecimalsetScale(int newScale, RoundingMode roundingMode)Returns
a BigDecimal whose scale is the specified value, and whose unscaled value is determined by multiplying or dividing this BigDecimal's unscaled value by the appropriate power of ten to maintain its overall value.
 

通过scale()方法可以获取精度,很奇怪,不是getScale()。

intscale()Returns the scale of this BigDecimal.


默认精度是多少

上述的设置和获取不是我要说的重点,我想说的是默认精度是多少,我之前自然而然的以为BigDecimal能够满足金钱的高精度计算,它确实可以,但是默认精度问题可能会让你掉进大坑。

 

先举例,看看一下BigDecimal的精度各是多少:

System.out.println(new BigDecimal(1).scale());
System.out.println(new BigDecimal(1.0).scale());
System.out.println(new BigDecimal(1.00).scale());
System.out.println(new BigDecimal(1.1).scale());
System.out.println(new BigDecimal(1.11).scale());
System.out.println(new BigDecimal(1.1111).scale());
System.out.println(new BigDecimal(11.1).scale());
System.out.println(new BigDecimal(111.1).scale());
System.out.println(new BigDecimal(1111.1).scale());
System.out.println(new BigDecimal(11111.1).scale());
System.out.println(new BigDecimal("1").scale());
System.out.println(new BigDecimal("1.0").scale());
System.out.println(new BigDecimal("1.00").scale());
System.out.println(new BigDecimal("1.1").scale());
System.out.println(new BigDecimal("1.11").scale());
System.out.println(new BigDecimal(999999999999999.1).scale());
System.out.println(new BigDecimal(999999999999999.1));
System.out.println(new BigDecimal("9999999999999999999999999999999999999999.000000").scale());
System.out.println(new BigDecimal("9999999999999999999999999999999999999999.000000"));
System.out.println(new BigDecimal("9999999999999999999999999999999999999999.000000").divide(new BigDecimal("9"),RoundingMode.HALF_DOWN)));


下面是打印结果,对照看后面的注释部分。

System.out.println(new BigDecimal(1).scale()); // 0
System.out.println(new BigDecimal(1.0).scale()); // 0
System.out.println(new BigDecimal(1.00).scale()); // 0
System.out.println(new BigDecimal(1.1).scale()); // 51
System.out.println(new BigDecimal(1.11).scale()); // 52
System.out.println(new BigDecimal(1.1111).scale()); // 51
System.out.println(new BigDecimal(11.1).scale()); // 49
System.out.println(new BigDecimal(111.1).scale()); // 45
System.out.println(new BigDecimal(1111.1).scale()); // 41
System.out.println(new BigDecimal(11111.1).scale()); // 39
System.out.println(new BigDecimal("1").scale()); // 0
System.out.println(new BigDecimal("1.0").scale()); // 1
System.out.println(new BigDecimal("1.00").scale()); // 2
System.out.println(new BigDecimal("1.1").scale()); // 1
System.out.println(new BigDecimal("1.11").scale()); // 2
System.out.println(new BigDecimal(999999999999999.1).scale()); // 3
System.out.println(new BigDecimal(999999999999999.1)); // 999999999999999.125
System.out.println(new BigDecimal("9999999999999999999999999999999999999999.000000").scale()); // 6
System.out.println(new BigDecimal("9999999999999999999999999999999999999999.000000")); // 9999999999999999999999999999999999999999.000000
System.out.println(new BigDecimal("9999999999999999999999999999999999999999.000000").divide(new BigDecimal("9"),RoundingMode.HALF_DOWN))); // 1111111111111111111111111111111111111111.000000


根据上面的打印结果来进行分析:

     1) 对于整型,精度就是0

     2) 对于double型

          小数点后面是0的话,精度就是0;

          小数点后面不是0,则取决于整数位的位数,整数位越多,精度越小;当整数位接近15位时,精度就趋于0,再多1位数据可能开始溢出(具体情况取决于double类型的数值表示)。

     3) 对于字符串类型的构造函数,小数点后面有几位,精度就是几位,而且几乎没有位数限制(试了40位都没有问题)


总结

尽量使用字符串来构建BigDecimal对象,这样不会受到到double本身取值范围限制的影响。

System.out.println(new BigDecimal("1.000000").scale());


或者可以在初始化BigDecimal对象后,显示为设置一次精度。

BigDecimal orderAmount = new BigDecimal(0);
orderAmount = orderAmount.setScale(6, RoundingMode.HALF_EVEN);


另外,BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,在运算时(包括setScale)千万要保存操作后的值。往往我们会使用连续调用,最后保存一次值。

BigDecimal orderMonthAmount; // 省略orderMonthAmount的初始化
BigDecimal orderValue = orderMonthAmount.divide(new BigDecimal(30), RoundingMode.HALF_EVEN)
.multiply(new BigDecimal(interval))
.setScale(2, BigDecimal.ROUND_HALF_EVEN);



参考

BigDecimal API

http://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html

 

Java.math.BigDecimal.scale()方法实例

http://www.yiibai.com/java/math/bigdecimal_scale.html

 

Java BigDecimal详解

http://blog.csdn.net/jackiehff/article/details/8582449

谢谢!

http://www.alanzeng.cn/2017/01/bigdecimal-sacle/

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  BigDecimal Java 精度 Scale