Java项目日常开发中使用BigDecimal常见问题总结

Java项目中有计算精度要求高的场景(如金额计算)会使用BigDecimal类型来代替DoubleFloat
本文整理了一些日常开发中使用BigDecimal值得注意的问题和代码实例。

  1. BigDecimal初始化时入参应使用String类型

例1:

BigDecimal x = new BigDecimal(3.33);
BigDecimal y = new BigDecimal("3.33");
System.out.println("x=" + x);
System.out.println("y=" + y);

输出:

x=3.3300000000000000710542735760100185871124267578125
y=3.33

使用double类型初始化的BigDecimal对象,出现了精度丢失的情况,应使用String类型值来初始化。

  1. BigDecimal初始化使用String类型时,需注意字符串前后不要有空白符

例2:

BigDecimal a = new BigDecimal("102.7");
System.out.println(a);

BigDecimal b = null;
try {
    b = new BigDecimal("102.7 ");
    System.out.println(b);
} catch (Exception e) {
    e.printStackTrace();
}

输出:

java.lang.NumberFormatException
	at java.math.BigDecimal.<init>(BigDecimal.java:497)
	at java.math.BigDecimal.<init>(BigDecimal.java:383)
	at java.math.BigDecimal.<init>(BigDecimal.java:809)
    ...

构建BigDecimal对象失败,抛NumberFormatException
如果字符串数值是第三方传入、mq消息、不小心人工输入空格等,需要注意这点。

  1. BigDecimal乘法运算结果格式化

例3:

BigDecimal c = new BigDecimal("13.14");
System.out.println(c.multiply(new BigDecimal("100")).toString());

输出:

1314.00

直接toString()方法,输出结果在小数点后多了2位0,如果想保留整数,可使用toBigInteger()方法。

// 1314
System.out.println(c.multiply(new BigDecimal("100")).toBigInteger());
  1. BigDecimal除法运算可能有除不尽的情况,应使用带scale参数的divide方法

例4:

try {
    BigDecimal nonTerminatingDecimal = new BigDecimal("1").divide(new BigDecimal("3"));
    System.out.println("nonTerminatingDecimal=" + nonTerminatingDecimal);
} catch (Exception e) {
    // e.printStackTrace();
}

输出:

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
	at java.math.BigDecimal.divide(BigDecimal.java:1693)
    ...

因为1除3除不尽,结果是无限循环小数,因此抛了ArithmeticException,应使用带scale参数的divide方法,如指定保留小数位数、舍入模式。

BigDecimal nonTerminatingDecimal2 = new BigDecimal("1").divide(new BigDecimal("3"), 2, RoundingMode.HALF_DOWN);
// 0.33
System.out.println(nonTerminatingDecimal2);
  1. BigDecimal做运算时注意次序问题

例5:

public static void main(String[] args) {
    Integer totalCount;
    Integer successCount;
    Integer errorCount;

    totalCount = 618;
    successCount = 60;
    errorCount = 18;
    // 12.48 ERROR
    calProgress1(totalCount, successCount, errorCount);
    // 12.62 OK
    calProgress2(totalCount, successCount, errorCount);

    System.out.println(StringUtils.center("分隔线", 50, "-"));

    totalCount = 61800;
    successCount = 60;
    errorCount = 18;
    // 0.00 ERROR
    calProgress1(totalCount, successCount, errorCount);
    // 0.13 ERROR
    calProgress2(totalCount, successCount, errorCount);
}

private static void calProgress1(Integer totalCount, Integer successCount, Integer errorCount) {
    BigDecimal calProgress = new BigDecimal(String.valueOf(successCount + errorCount))
            .multiply(new BigDecimal("100")
            .divide(new BigDecimal(String.valueOf(totalCount)), 2, RoundingMode.HALF_UP));
    System.out.println(calProgress.toString());
}

private static void calProgress2(Integer totalCount, Integer successCount, Integer errorCount) {
    BigDecimal calProgress2 = new BigDecimal(String.valueOf(successCount + errorCount))
            .multiply(new BigDecimal("100"))
            .divide(new BigDecimal(String.valueOf(totalCount)), 2, RoundingMode.HALF_UP);
    System.out.println(calProgress2.toString());
}

输出:

12.48
12.62
-----------------------分隔线------------------------
0.00
0.13

程序逻辑是计算百分比,公式:(成功数+失败数)*100/总数;

注意calProgress1calProgress2里代码的差别:
calProgress1方法里multiply(new BigDecimal("100")少写了个),导致先做了100/总数除法运算。

2个方法都能编译通过运行;
第1组输出由于数据量较小,结果差异不大,看上去都能计算出百分比,
第2组输出加大了总数量,calProgress1输出结果为0.00,丢失了部分精度。

例5是来自项目里的实例,某界面上展示数据处理的百分比进度,测试环境数据量较小,界面上看上去没问题,能从0-100%正常展示;
线上环境数据量较大,程序处理数据,界面上长时间展示0.00%。
修正为calProgress2里的代码,保证严格按照公式计算,先做乘法最后除法运算,除法运算指定保留小数位数和舍入方式。

posted @ 2023-09-03 22:28  cdfive  阅读(185)  评论(0编辑  收藏  举报