Java BigDecimal初探
Posted liycode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java BigDecimal初探相关的知识,希望对你有一定的参考价值。
更新时间:2016-03-17
一、引言
《Effactive Java》中有这样的描述:float
和double
类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,货币计算往往要求结果精确,这时候可以使用int
、long
或BigDecimal
。
二、不可变性
BigDecimal
是不可变类,每一个操作(加减乘除等)都会返回一个新的对象, 下面以加法操作为例:
BigDecimal a =new BigDecimal("1.22");
System.out.println("construct with a String value: " + a);
BigDecimal b =new BigDecimal("2.22");
a.add(b);
System.out.println("a plus b is : " + a);
我们很容易会认为会输出:
construct with a String value: 1.22
a plus b is :3.44
但实际上a plus b is : 1.22
因为BigDecimal
是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以a.add(b)
虽然做了加法操作,但是a并没有保存加操作后的值,正确的用法应该是a=a.add(b);
减乘除操作也是一样的返回一个新的BigDecimal
对象。
三、构造函数和valueOf方法
首先看如下一段代码:
// use constructor BigDecimal(double)
BigDecimal aDouble =new BigDecimal(1.22);
System.out.println("construct with a double value: " + aDouble);
// use constructor BigDecimal(String)
BigDecimal aString = new BigDecimal("1.22");
System.out.println("construct with a String value: " + aString);
// use constructor BigDecimal.valueOf(double)
BigDecimal aValue = BigDecimal.valueOf(1.22);
System.out.println("use valueOf method: " + aValue);
你认为输出结果会是什么呢?如果你认为第一个会输出1.22,那么恭喜你答错了,输出结果如下:
construct with a double value: 1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22
use valueOf method: 1.22
为什么会这样呢?JavaDoc对于BigDecimal(double)
有很详细的说明:
1、参数类型为double
的构造方法的结果有一定的不可预知性。有人可能认为在Java中new BigDecimal(0.1)
所创建的BigDecimal
的值正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double
(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
2、另一方面,String
构造方法是完全可预知的:new BigDecimal("0.1")
将创建一个 BigDecimal,它的值正好等于期望的0.1。因此,比较而言,通常建议优先使用String
构造方法。
3、当 double
必须用作BigDecimal
的来源时,请注意,此构造方法提供了一个精确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)
方法将double
转换为String
,然后使用BigDecimal(String)
构造方法。要获取该结果,使用static valueOf(double)
方法。
BigDecimal.valueOf(double)
使用由 Double.toString(double)
方法提供的 double
的标准化字符串表示形式( canonical string representation) 将 double
转换成 BigDecimal
。这也是比较推荐的一种方式。
BigDecimal.valueOf(double)
还有一个重载的方法 BigDecimal.valueOf(long)
,对于某些常用值(0到10) BigDecimal
在内部做了缓存, 如果传递的参数值范围为[0, 10], 这个方法直接返回缓存中相应的BigDecimal
对象。
四、equals方法
BigDecimal.equals
方法是有问题的。仅当你确定比较的值有着相同的标度时才可使用。因此,当你校验相等性时注意BigDecimal
有一个标度,用于相等性比较。而compareTo
方法则会忽略这个标度(scale)。
参见以下测试代码:
// 打印false
System.out.println(new BigDecimal("0.0").equals(new BigDecimal("0.00")));
// 打印false
System.out.println(new BigDecimal("0.0").hashCode() == (new BigDecimal("0.00")).hashCode());
// 打印0
System.out.println(new BigDecimal("0.0").compareTo(new BigDecimal("0.00")));
五、对除法使用标度
BigDecimal
对象的精度没有限制。如果结果不能终止,divide方法将会抛出ArithmeticException
, 如1 / 3 = 0.33333...。所以强烈推荐使用重载方法divide(BigDecimal d, int scale, int roundMode)
指定标度和舍入模式来避免以上异常。
关于舍入模式常用的有BigDecimal.ROUND_HALF_UP
,也就是四舍五入。
六、总结
1、商业计算(要求精确结果)时使用BigDecimal
。
2、使用参数类型为String
的构造函数,将double
转换成BigDecimal
时用BigDecimal.valueOf(double)
,做除法运算时使用重载的方法divide(BigDecimal d, int scale, int roundMode)
。
3、BigDecimal是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。
4、尽量使用compareTo
方法比较两个BigDecimal
对象的大小。
以上是关于Java BigDecimal初探的主要内容,如果未能解决你的问题,请参考以下文章
java中BigDecimal如何求余(即实现%的功能),求代码
如何在java代码中使用elasticsearch painless BigDecimal?