本文首发于我的微信公众号《andyqian》, 期待你的关注!java
在Java中,咱们一般使用 BigDecimal 类型来表示金额,特别是在金融,财务系统中,使用的特别多。例如:转帐金额,手续费等等。今天就一块儿来认识下BigDecimal。微信
在此以前,咱们先来说讲为何要使用 BigDecimal ?而不是Float,Double类型?其实光从表现形式来看,Float,Double,BigDecimal 类型都能表示小数。其区别在于精确计算时,Float 与 Double 类型都会损失精度,固然了,BigDecimal 使用不正确时,也会损失精度。在金融系统中,金额计算是最基本的运算,精度的丢失是绝对不能容忍的。接下来,咱们来看看下面的例子:ide
Float 类型:函数
public void testFloat(){ float a = 1.1f; float b = 0.8f; System.out.println("a-b = "+(a-b)); System.out.println("a+b = "+(a+b)); System.out.println("a*b = "+(a*b)); System.out.println("a/b = "+(a/b)); }
结果以下:测试
a-b = 0.3 a+b = 1.9000001 a*b = 0.88000005 a/b = 1.375
Double 类型:spa
public void testDouble(){ double a = 1.1; double b = 0.8; System.out.println("a-b = "+(a-b)); System.out.println("a+b = "+(a+b)); System.out.println("a*b = "+(a*b)); System.out.println("a/b = "+(a/b)); }
结果以下:线程
a-b = 0.30000000000000004 a+b = 1.9000000000000001 a*b = 0.8800000000000001 a/b = 1.375
BigDecmial 错误使用:3d
public void testBigDecimal(){ BigDecimal a = new BigDecimal(1.1); BigDecimal b = new BigDecimal(0.8); System.out.println("a-b = "+(a.subtract(b))); System.out.println("a+b = "+(a.add(b))); System.out.println("a*b = "+(a.multiply(b))); System.out.println("a/b = "+(a.divide(b))); }
结果以下:code
a-b = 0.3000000000000000444089209850062616169452667236328125 a+b = 1.9000000000000001332267629550187848508358001708984375 a*b = 0.8800000000000001199040866595169103100567462588676208086428264139311483660321755451150238513946533203125 java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
正确使用方法:orm
public void testBigDecimalNormal(){ BigDecimal a = new BigDecimal("1.1"); BigDecimal b = new BigDecimal("0.8"); System.out.println("a-b = "+(a.subtract(b))); System.out.println("a+b = "+(a.add(b))); System.out.println("a*b = "+(a.multiply(b))); System.out.println("a/b = "+(a.divide(b))); }
结果以下:
a-b = 0.3 a+b = 1.9 a*b = 0.88 a/b = 1.375
经过上面的例子,咱们能够清晰的看出。除了正确使用BigDecimal类型外,其他的在计算过程当中,均损失精度。所以咱们能够得出如下结论:
在须要精度计算数值时,不该该使用float,double 类型,进行计算。
BigDecimal 应该使用 String 构造函数,禁止使用double构造函数。
其实,在使用BigDecimal过程,也有许多须要注意的细节。
科学计数法问题
@Test public void testBigDecimalResult(){ BigDecimal b = new BigDecimal("0.0000001"); System.out.println(b.toString()); System.out.println(b.toPlainString()); }
执行结果:
1E-7 0.0000001
结论:当 BigDecimal的值 小于必定值时(测试时发现:小于等于0.0000001)时,则会被记为科学计数法。可使用 toPlainString()
方法显示原来的值。
2. 去除多余的 0
@Test public void testBigDecimalStripZeros(){ BigDecimal b = new BigDecimal("0.000000100000000"); System.out.println(b.stripTrailingZeros().toString()); System.out.println(b.stripTrailingZeros().toPlainString()); }
使用场景:去除多余的0,当金额有小数位限制时,使用该方法可以去除掉无效的0,从而达到自动修复无效参数的目的。
结论:stripTrailingZeros() 方法的本质是去除掉多余的0,其返回数据类型是BigDecimal,一样的在使用时须要注意科学技术法的问题。
3. 保留小数位
@Test public void testBigDecimalStripZeros(){ BigDecimal d = new BigDecimal("1.2222"); d.setScale(2); System.out.println(d.toPlainString()); }
运行结果:
java.lang.ArithmeticException: Rounding necessary
缘由:在setScale()方法中的roundingMode属性设置为了ROUND_UNNECESSARY,代码以下:
public BigDecimal setScale(int newScale) { return setScale(newScale,); }
而在:
java.math.BigDecimal.commonNeedIncrement(BigDecimal.java:4179)
中ROUND_UNNECESSARY 类型偏偏会抛出异常。代码显示以下:
private static boolean commonNeedIncrement(int roundingMode, int qsign, int cmpFracHalf, boolean oddQuot) { switch(roundingMode) { case ROUND_UNNECESSARY: throw new ArithmeticException("Rounding necessary"); case ROUND_UP: // Away from zero return true; ...
经过上面的例子,如今咱们已经知道了BigDecimal的一些使用细节。其实呀,这些都是血淋淋的教训换来的经验,每个小细节对应的都是一个个事故,记忆犹新。这里推荐你们都抽时间看看《Java开发手册》,就能避免掉不少坑。
上面的问题,在《Java开发手册》中一样有写到:
【强制】为了防止精度损失,禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。
说明:BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会致使业务逻辑异常。如:
BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.10000000149正例:优先推荐入参为String 的构造函数,或使用BigDecimal的valueOf方法。
相关阅读:
扫码关注,一块儿进步
我的博客: http://www.andyqian.com