天天看点

bigdecimal 保留两位小数_你所不知道的 BigDecimal

bigdecimal 保留两位小数_你所不知道的 BigDecimal
本文首发于个人微信公众号《andyqian》,期待你的关注!

前言

在Java中,我们通常使用 BigDecimal 类型来表示金额,特别是在金融,财务系统中,使用的特别多。例如:转账金额,手续费等等。今天就一起来认识下BigDecimal。

为什么是BigDecimal ?

在此之前,我们先来讲讲为什么要使用 BigDecimal ?而不是Float,Double类型?其实光从表现形式来看,Float,Double,BigDecimal 类型都能表示小数。其区别在于精确计算时,Float 与 Double 类型都会损失精度,当然了,BigDecimal 使用不正确时,也会损失精度。在金融系统中,金额计算是最基本的运算,精度的丢失是绝对不能容忍的。接下来,我们来看看下面的例子:

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 类型:

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 错误使用:

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)));
}           

结果如下:

a-b = 0.3000000000000000444089209850062616169452667236328125
a+b = 1.9000000000000001332267629550187848508358001708984375
a*b = 0.8800000000000001199040866595169103100567462588676208086428264139311483660321755451150238513946533203125

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.           

正确使用方法:

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类型外,其余的在计算过程中,均损失精度。因此我们可以得出以下结论:

  1. 在需要精度计算数值时,不应该使用float,double 类型,进行计算。
  2. BigDecimal 应该使用 String 构造函数,禁止使用double构造函数。

使用细节

其实,在使用BigDecimal过程,也有许多需要注意的细节。

  1. 科学计数法问题
@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方法。

相关阅读:

《软件之路 之 项目外包》

《ThreadPoolExecutor 原理解析》

《Java线程池ThreadPoolExecutor》

《再谈Java 生产神器 BTrace》

继续阅读