什么是BigDecimal

望文生义一下,大十进制?了解它之前首先需要弄懂 0.1 + 0.2 =? 0.3 这个式子在Java中是否成立?
并不成立!因为上式的运算涉及转为二进制再转为十进制,0.1在转为二进制时会无限循环,标准位的限制后会截断,这就出现了精度损失,所以引入BigDecimal来解决大整数及小数计算问题。

官方描述:

不可变的、任意精度的有符号十进制数。 BigDecimal由一个任意精度的整数非标度值和一个 32 位整数标度组成。BigDecimal类提供算术、缩放操作、舍入、比较、散列和格式转换的操作。

如何使用

继承结构:

  1. public class BigDecimal extends Number implements Comparable<BigDecimal> {

从源码上建继承了Number并且实现了Comparable,说明BigDecimal支持排序Number是数值类型的基类,提供了将包装类拆箱成基本类型的方法。

变量:

  1. // 小数位个数
  2. private final int scale;
  3. // 精度
  4. private transient int precision;
  5. // 数值0 刻度为0
  6. public static final BigDecimal ZERO = zeroThroughTen[0];
  7. // 数值1 刻度为0
  8. public static final BigDecimal ONE = zeroThroughTen[1];
  9. // 数值10 刻度为0
  10. public static final BigDecimal TEN = zeroThroughTen[10];

八种舍入:

  • ROUND_UP,向远离零的方向舍入

    1. BigDecimal bd = new BigDecimal("6.45100100");
    2. bd = bd.setScale(3, BigDecimal.ROUND_UP);
    3. System.out.println(bd);
    4. // 输出:6.452 即:小数位后存在不为0的数就增加一个值,正负数同样规则
  • ROUND_DOWN,向接近零的方向舍入

    1. BigDecimal bd = new BigDecimal("6.4519000");
    2. bd = bd.setScale(3, BigDecimal.ROUND_DOWN);
    3. System.out.println(bd);
    4. // 输出:6.451 即:抛弃3个小数位后的所有数值,正负数同样规则
  • ROUND_CEILING,向正无穷大的方向舍入

    如果 BigDecimal 为正,则舍入行为与ROUND_UP 相同,如果为负,则舍入行为与 ROUND_DOWN 相同

  • ROUND_FLOOR,向下(小)取整

    1. BigDecimal bd = new BigDecimal("6.4519000");
    2. bd = bd.setScale(3, BigDecimal.ROUND_FLOOR);
    3. System.out.println(bd);
    4. // 输出:6.451 负数则输出-6.452
  • ROUND_HALF_UP,正规军!四舍五入

  • ROUND_HALF_DOWN,五舍六入

    就是大于5的时候舍入,小于等于5的时候舍弃

  • ROUND_HALF_EVEN,银行家舍入

    四舍六入,如果是5的话,要看5后面一位是否是0,如果不是0,舍入的时候进1;如果是0,然后看5之前的数是奇书还是偶数,奇书同样是进一,偶数的话就舍去

  • ROUND_UNNECESSARY

    表示该数值是精确的,如果某一个数值指定了该模式,并且不是精确的,则会抛出异常

常用方法:

  • add:加法运算

    1. // 保留3位小数,四舍五入
    2. BigDecimal bd = new BigDecimal("6.4519000");
    3. BigDecimal bc = new BigDecimal("3.1415927");
    4. BigDecimal add = bd.add(bc).setScale(3, BigDecimal.ROUND_HALF_UP);
    5. // 9.593
  • subtract:减法运算

    1. BigDecimal bd = new BigDecimal("6.4519000");
    2. BigDecimal bc = new BigDecimal("3.1415927");
    3. BigDecimal sub = bd.subtract(bc).setScale(3, BigDecimal.ROUND_HALF_UP);
    4. // 3.310
  • multiply:乘法

    1. BigDecimal bd = new BigDecimal("6.4519000");
    2. BigDecimal bc = new BigDecimal("3.1415927");
    3. BigDecimal mul = bd.multiply(bc).setScale(3, BigDecimal.ROUND_HALF_UP);
    4. // 20.269
  • divide:除法

    1. BigDecimal bd = new BigDecimal("6.4519000");
    2. BigDecimal bc = new BigDecimal("3.1415927");
    3. BigDecimal div = bd.divide(bc, 3, BigDecimal.ROUND_HALF_UP);
    4. // 2.054 注意:除法的bc(分母)不能为0,否则报错
  • compareTo:比较

    1. // 与0比较,需要使用BigDecimal自己封装的基础数据
    2. a.compareTo(BigDecimal.ZERO)
  • toStringtoPlainStringtoEngineeringString计数

    三个方法都是用于BigDecimal的字符串表示,不同的是toString有可能会使用科学记数法,toPlainString只展示数值,不使用科学记数法,toEngineeringString工程计数法,与科学技术法类似,但要求10的幂必须是3的倍数;

  1. // 第一种
  2. BigDecimal bigDecimal = new BigDecimal("1000000000");
  3. // 第二种
  4. BigDecimal bigDecimal = new BigDecimal("100000");
  5. System.out.println(bigDecimal.stripTrailingZeros().toPlainString());
  6. System.out.println(bigDecimal.stripTrailingZeros().toString());
  7. System.out.println(bigDecimal.stripTrailingZeros().toEngineeringString());
  8. // 1000000000
  9. // 1E+9
  10. // 1E+9
  11. // 100000
  12. // 1E+5
  13. // 100E+3
  • divideToIntegralValue:商的整数部分

    1. BigDecimal b1 = new BigDecimal("20");
    2. BigDecimal b2 = new BigDecimal("3");
    3. System.out.println(b1.divideToIntegralValue(b2).setScale(3,BigDecimal.ROUND_HALF_UP));
    4. // 6.000
  • remainder:求余数

    1. System.out.println(b1.remainder(b2).setScale(3, BigDecimal.ROUND_HALF_UP));
    2. // 2.000
  • divideAndRemainder: 商的整数和余数部分(数组)

    1. System.out.println(Arrays.toString(b1.divideAndRemainder(b2)));
    2. // [6, 2]
  • abs:绝对值

注意:
BigDecimal不可变,同String一样,

  1. BigDecimal b = new BigDecimal("3.012");
  2. // 获取其-this值
  3. b.negate();
  4. b = b.negate();
  5. System.out.println(b);
  6. // -3.012

MathContext类
使用BigDecimal的时候,需要同时指定精度和舍入模式的话,可以使用MathContext类
该对象是对BigDecimal中精度和舍入模式的一个封装,也就是可以指定有效位数和具体的舍入模式

  1. System.out.println(new BigDecimal("3.1415927", new MathContext(4, RoundingMode.HALF_UP)));
  2. // 3.142 也可以通过下面的方法
  3. BigDecimal b = new BigDecimal("3.012");
  4. System.out.println(b.setScale(2, BigDecimal.ROUND_HALF_UP));
  5. // 3.01

聚合计算

场景一:直接求和
举个栗子:

  1. List<StudentB> studentList = new ArrayList<>();
  2. studentList.add(new StudentB("张三", new BigDecimal("12.12")));
  3. studentList.add(new StudentB("张三1", new BigDecimal("23.123")));
  4. studentList.add(new StudentB("张三2", null));
  5. studentList.add(new StudentB("张三3", new BigDecimal("112.78")));

求所有的数值和,并保留2位小数:

  1. BigDecimal reduce = studentList.stream()
  2. .map(StudentB::getMoney)
  3. .filter(Objects::nonNull)
  4. .reduce(BigDecimal.ZERO, BigDecimal::add)
  5. .setScale(2, BigDecimal.ROUND_HALF_UP);
  6. System.out.println(reduce); // 148.02

这里是借助reduce函数完成运算,起始数值为0,依次累加,过滤为null的数据。

场景二:分组求和
举个栗子:

  1. List<StudentB> studentList = new ArrayList<>();
  2. studentList.add(new StudentB("A", "张三", new BigDecimal("12.12")));
  3. studentList.add(new StudentB("B", "张三1", new BigDecimal("233.123")));
  4. studentList.add(new StudentB("A", "张三2", new BigDecimal("133.123")));
  5. studentList.add(new StudentB("A", "张三3", new BigDecimal("112.78")));

按照分组求和:

  1. Map<String, BigDecimal> result = studentList.stream()
  2. .filter(studentB -> Objects.nonNull(studentB.getMoney()))
  3. .collect(Collectors.groupingBy(StudentB::getGroup,
  4. Collectors.mapping(StudentB::getMoney,
  5. Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
  6. System.out.println(result); // {A=258.023, B=233.123}

场景三:最大值
栗子:数据同上

  1. BigDecimal max = studentList.stream()
  2. .map(StudentB::getMoney)
  3. .filter(Objects::nonNull)
  4. .max(BigDecimal::compareTo).get();
  5. System.out.println(max); // 233.123

场景四:最小值
栗子:数据同上

  1. BigDecimal min = studentList.stream()
  2. .map(StudentB::getMoney)
  3. .filter(Objects::nonNull)
  4. .min(BigDecimal::compareTo).get();
  5. System.out.println(min); // 12.12

场景五:平均值
栗子:数据同上

  1. BigDecimal average = studentList.stream()
  2. .map(StudentB::getMoney)
  3. .reduce(BigDecimal.ZERO, BigDecimal::add)
  4. .divide(BigDecimal.valueOf(studentList.size()), 2, BigDecimal.ROUND_HALF_UP);
  5. System.out.println(average); // 122.79

场景六:生成Map
栗子:

  1. // 1.根据roleCode分组 J甲方3, Y乙方1, B第三方2 并自动完成升序排序
  2. Map<Integer, List<ContractTradingDTO>> groupTradings = tradingList.stream()
  3. .collect(Collectors.groupingBy(trading -> {
  4. if (TradingRoleCode.FIRST.value().equals(trading.getRoleCode())) {
  5. return 3;
  6. } else if (TradingRoleCode.SECOND.value().equals(trading.getRoleCode())) {
  7. return 1;
  8. } else {
  9. return 2;
  10. }
  11. }, TreeMap::new, Collectors.toList()));