在java语言中,double和float用于二进制浮点型计算,没法获得精确的结果。而BigDecimal则用于精确的计算。不超过16位有效数字(最好是不超过13位)的科学和工程计算,可使用double和float,但要求精确计或者超过了16位有效数字(超过13位也建议如此)的商业运算则须要使用BigDecimal进行运行,好比金融行业。html
《Effactive Java》第2版第48条中提到,“float和double类型尤为不适合用于货币计算,由于要让一个float或者double精确地标识0.1(或者10的任何其余负数次方值)是不可能的。” 若是不进行运算,使用String表示金额都比double和float强,固然最好的是BigDecimal。金融行业http通讯接口可使用String来表示金额,若是内部接口调用,建议使用BigDecimal。java
BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。若是为零或正数,则标度是小数点后的位数。若是为负数,则将该数的非标度值乘以 10 的负scale 次幂。所以,BigDecimal表示的数值是(unscaledValue × 10-scale)。ide
分四类,一类是经过另外一个BigDecimal对象构造,一类是使用double/long/int/BigInteger类型的数值构造,一类是经过String字符串构造,最后一类是使用静态方法valueOf()构造,静态方法要求参数是double/long/int/BigInteger等。post
JDK API文档中,对于public BigDecimal(double val) 解释以下:测试
注:this
Double.toString(double)
方法,而后使用 BigDecimal(String)
构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double)
方法。对于public static BigDecimal valueOf(long val)的解释以下:url
故此,对于金融行业中的使用有如下结论:spa
1) 优先使用String参数的构造方法;.net
2)若是须要经过double构造BigDecimal,有限使用valueOf(double val)方法;这样获得的BigDecimal和String参数的构造方法标度一致(即标度可控);code
3)valueOf()的静态工厂方法优先于long、int等类型参数的构造方法,
4)若是想获取指定标度的初始化数值,好比2位小数的0,能够以下使用:
BigDecimal zero1 = new BigDecimal("0.00");
String的构造方法,会根据字符串的小数位来设定标度,其余的构造方法还须要额外调用setScale()来设定标度。
BigDecimal类的标度使用scale属性表示,就是小数位数,精度使用precision属性表示,就是有效位数(亦即整数位+小数位).
舍入方式有多种,BigDecimal类自己有舍入方式的静态成员字段,可是已经废弃,建议使用RoundingMode枚举值。
RoundingMode枚举值以下:
枚举常量摘要 | |
---|---|
CEILING 向正无限大方向舍入的舍入模式。 |
|
DOWN 向零方向舍入的舍入模式。 |
|
FLOOR 向负无限大方向舍入的舍入模式。 |
|
HALF_DOWN 向最接近数字方向舍入的舍入模式,若是与两个相邻数字的距离相等,则向下舍入。 |
|
HALF_EVEN 向最接近数字方向舍入的舍入模式,若是与两个相邻数字的距离相等,则向相邻的偶数舍入。 |
|
HALF_UP 向最接近数字方向舍入的舍入模式,若是与两个相邻数字的距离相等,则向上舍入。 |
|
UNNECESSARY 用于断言请求的操做具备精确结果的舍入模式,所以不须要舍入。 |
|
UP 远离零方向舍入的舍入模式。 |
其中HALF_UP就是最经常使用的四舍五入,其余根据英文名字很好理解。
若是只是对货币金额值的存取,使用String也能够,但若是金额要参与四则运算,则必须使用BigDecimal进行精确的运算。BigDecimal与double、float的运算相比,BigDecimal运算比double、float运算精确,但速度没有double、float的快。
BigDecimal的四则运算使用如下方法:public BigDecimal add(BigDecimal value); //加法
public BigDecimal subtract(BigDecimal value); //减法
public BigDecimal multiply(BigDecimal value); //乘法
public BigDecimal divide(BigDecimal value); //除法
JDK API中对四则运算的标度解释以下:
对于全部算术运算符,运算的执行方式是,首先计算准确的中间结果,而后,使用选择的舍入模式将其舍入为精度设置(若有必要)指定的位数。若是不返回准确结果,则将丢弃准确结果的某些数位。当舍入增长了返回结果的大小时,前导数字“9”的进位传播可能会建立新的数位。例如,将值 999.9 舍入为三位数字,则在数值上等于一千,表示为 100×101。在这种状况下,新的 "1" 是返回结果的前导数位。
除了逻辑的准确结果外,每种算术运算都有一个表示结果的首选标度。下表列出了每一个运算的首选标度。
算术运算结果的首选标度运算 | 结果的首选标度 |
---|---|
加 | max(addend.scale(), augend.scale()) |
减 | max(minuend.scale(), subtrahend.scale()) |
乘 | multiplier.scale() + multiplicand.scale() |
除 | dividend.scale() - divisor.scale() |
这些标度是返回准确算术结果的方法使用的标度;准确相除可能必须使用较大的标度除外,由于准确的结果可能有较多的位数。例如,1/32 获得 0.03125。
舍入以前,逻辑的准确中间结果的标度是该运算的首选标度。
由上面能够看出来,若是调用上述四个方法的话,计算结果的标度可能不受控制,BigDecimal还为每一个运算提供了几个重载方法,能够控制运算结果的标度和舍入方式。
以divide为例进行解释:
JDK API对public BigDecimal divide(BigDecimal divisor) 的解释以下:
divisor
- 此
BigDecimal 要相除的值。
ArithmeticException
- 若是准确的商值没有无穷的十进制扩展
能够看出,没法精确表示商值时就会抛异常,而除法很大可能没法精确表示商值。因此,必须指定标度和舍入方式。如下几个重载方法能够供调用者指定标度和舍入方式。
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
public BigDecimal divide(BigDecimal divisor, MathContext mc)
这些方法也可能抛出异常,但异常的状况是:divisor==0 或者 roundingMode==ROUND_UNNECESSARY 和 this.scale() 不足以准确地表示相除的结果。因此只要设置好标度和舍入方式,基本能够保证不会抛异常。
6. BigDecimal的不可变性
BigDecimal有一个相似String的特性,就是不可变性。调用setScale或者add等运算方法后,原对象的scale和数值都不会改变,因此须要将方法的返回值进行保存使用。
好比:a = a.setScale(2); a = a.add(b). 其中a,b为BigDecimal对象。
7.总结
1)金融行业的金额必定要使用BigDecimal来表示;
2)BigDecimal最好使用String的构造方法建立,若是要使用double等数值做为参数,也要使用valueOf来建立对象。
3)BigDecimal进行四则运算时,最好指定其标度和舍入方式,不然可能抛异常。
4)BigDecimal是不可变的;四则运算后要使用对象保存结果;
5)多阅读相关API文档,写测试代码来验证其相关方法的调动,才能更好的掌握。
参考资料:
Java BigDecimal详解 (http://blog.csdn.net/jackiehff/article/details/8582449)
《Effactive Java》