什么是BigDecimal
望文生义一下,大十进制?了解它之前首先需要弄懂 0.1 + 0.2 =? 0.3 这个式子在Java中是否成立?
并不成立!因为上式的运算涉及转为二进制再转为十进制,0.1在转为二进制时会无限循环,标准位的限制后会截断,这就出现了精度损失
,所以引入BigDecimal来解决大整数及小数计算问题。
官方描述:
不可变的、任意精度的有符号十进制数。 BigDecimal由一个任意精度的整数非标度值和一个 32 位整数标度组成。BigDecimal类提供算术、缩放操作、舍入、比较、散列和格式转换的操作。
如何使用
继承结构:
public class BigDecimal extends Number implements Comparable<BigDecimal> {
从源码上建继承了Number
并且实现了Comparable
,说明BigDecimal支持排序
,Number
是数值类型的基类,提供了将包装类拆箱成基本类型的方法。
变量:
// 小数位个数
private final int scale;
// 精度
private transient int precision;
// 数值0 刻度为0
public static final BigDecimal ZERO = zeroThroughTen[0];
// 数值1 刻度为0
public static final BigDecimal ONE = zeroThroughTen[1];
// 数值10 刻度为0
public static final BigDecimal TEN = zeroThroughTen[10];
八种舍入:
ROUND_UP,向远离零的方向舍入
BigDecimal bd = new BigDecimal("6.45100100");
bd = bd.setScale(3, BigDecimal.ROUND_UP);
System.out.println(bd);
// 输出:6.452 即:小数位后存在不为0的数就增加一个值,正负数同样规则
ROUND_DOWN,向接近零的方向舍入
BigDecimal bd = new BigDecimal("6.4519000");
bd = bd.setScale(3, BigDecimal.ROUND_DOWN);
System.out.println(bd);
// 输出:6.451 即:抛弃3个小数位后的所有数值,正负数同样规则
ROUND_CEILING,向正无穷大的方向舍入
如果 BigDecimal 为正,则舍入行为与ROUND_UP 相同,如果为负,则舍入行为与 ROUND_DOWN 相同
ROUND_FLOOR,向下(小)取整
BigDecimal bd = new BigDecimal("6.4519000");
bd = bd.setScale(3, BigDecimal.ROUND_FLOOR);
System.out.println(bd);
// 输出: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:加法运算
// 保留3位小数,四舍五入
BigDecimal bd = new BigDecimal("6.4519000");
BigDecimal bc = new BigDecimal("3.1415927");
BigDecimal add = bd.add(bc).setScale(3, BigDecimal.ROUND_HALF_UP);
// 9.593
subtract:减法运算
BigDecimal bd = new BigDecimal("6.4519000");
BigDecimal bc = new BigDecimal("3.1415927");
BigDecimal sub = bd.subtract(bc).setScale(3, BigDecimal.ROUND_HALF_UP);
// 3.310
multiply:乘法
BigDecimal bd = new BigDecimal("6.4519000");
BigDecimal bc = new BigDecimal("3.1415927");
BigDecimal mul = bd.multiply(bc).setScale(3, BigDecimal.ROUND_HALF_UP);
// 20.269
divide:除法
BigDecimal bd = new BigDecimal("6.4519000");
BigDecimal bc = new BigDecimal("3.1415927");
BigDecimal div = bd.divide(bc, 3, BigDecimal.ROUND_HALF_UP);
// 2.054 注意:除法的bc(分母)不能为0,否则报错
compareTo:比较
// 与0比较,需要使用BigDecimal自己封装的基础数据
a.compareTo(BigDecimal.ZERO)
toString、toPlainString、toEngineeringString计数
三个方法都是用于BigDecimal的字符串表示,不同的是
toString
有可能会使用科学记数法,toPlainString
只展示数值,不使用科学记数法,toEngineeringString
工程计数法,与科学技术法类似,但要求10的幂必须是3的倍数;
// 第一种
BigDecimal bigDecimal = new BigDecimal("1000000000");
// 第二种
BigDecimal bigDecimal = new BigDecimal("100000");
System.out.println(bigDecimal.stripTrailingZeros().toPlainString());
System.out.println(bigDecimal.stripTrailingZeros().toString());
System.out.println(bigDecimal.stripTrailingZeros().toEngineeringString());
// 1000000000
// 1E+9
// 1E+9
// 100000
// 1E+5
// 100E+3
divideToIntegralValue:商的整数部分
BigDecimal b1 = new BigDecimal("20");
BigDecimal b2 = new BigDecimal("3");
System.out.println(b1.divideToIntegralValue(b2).setScale(3,BigDecimal.ROUND_HALF_UP));
// 6.000
remainder:求余数
System.out.println(b1.remainder(b2).setScale(3, BigDecimal.ROUND_HALF_UP));
// 2.000
divideAndRemainder: 商的整数和余数部分(数组)
System.out.println(Arrays.toString(b1.divideAndRemainder(b2)));
// [6, 2]
abs:绝对值
注意:
BigDecimal不可变,同String一样,
BigDecimal b = new BigDecimal("3.012");
// 获取其-this值
b.negate();
b = b.negate();
System.out.println(b);
// -3.012
MathContext类
使用BigDecimal的时候,需要同时指定精度和舍入模式的话,可以使用MathContext类
该对象是对BigDecimal中精度和舍入模式的一个封装,也就是可以指定有效位数和具体的舍入模式
System.out.println(new BigDecimal("3.1415927", new MathContext(4, RoundingMode.HALF_UP)));
// 3.142 也可以通过下面的方法
BigDecimal b = new BigDecimal("3.012");
System.out.println(b.setScale(2, BigDecimal.ROUND_HALF_UP));
// 3.01
聚合计算
场景一:直接求和
举个栗子:
List<StudentB> studentList = new ArrayList<>();
studentList.add(new StudentB("张三", new BigDecimal("12.12")));
studentList.add(new StudentB("张三1", new BigDecimal("23.123")));
studentList.add(new StudentB("张三2", null));
studentList.add(new StudentB("张三3", new BigDecimal("112.78")));
求所有的数值和,并保留2位小数:
BigDecimal reduce = studentList.stream()
.map(StudentB::getMoney)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println(reduce); // 148.02
这里是借助reduce函数完成运算,起始数值为0,依次累加,过滤为null的数据。
场景二:分组求和
举个栗子:
List<StudentB> studentList = new ArrayList<>();
studentList.add(new StudentB("A", "张三", new BigDecimal("12.12")));
studentList.add(new StudentB("B", "张三1", new BigDecimal("233.123")));
studentList.add(new StudentB("A", "张三2", new BigDecimal("133.123")));
studentList.add(new StudentB("A", "张三3", new BigDecimal("112.78")));
按照分组求和:
Map<String, BigDecimal> result = studentList.stream()
.filter(studentB -> Objects.nonNull(studentB.getMoney()))
.collect(Collectors.groupingBy(StudentB::getGroup,
Collectors.mapping(StudentB::getMoney,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
System.out.println(result); // {A=258.023, B=233.123}
场景三:最大值
栗子:数据同上
BigDecimal max = studentList.stream()
.map(StudentB::getMoney)
.filter(Objects::nonNull)
.max(BigDecimal::compareTo).get();
System.out.println(max); // 233.123
场景四:最小值
栗子:数据同上
BigDecimal min = studentList.stream()
.map(StudentB::getMoney)
.filter(Objects::nonNull)
.min(BigDecimal::compareTo).get();
System.out.println(min); // 12.12
场景五:平均值
栗子:数据同上
BigDecimal average = studentList.stream()
.map(StudentB::getMoney)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(studentList.size()), 2, BigDecimal.ROUND_HALF_UP);
System.out.println(average); // 122.79
场景六:生成Map
栗子:
// 1.根据roleCode分组 J甲方3, Y乙方1, B第三方2 并自动完成升序排序
Map<Integer, List<ContractTradingDTO>> groupTradings = tradingList.stream()
.collect(Collectors.groupingBy(trading -> {
if (TradingRoleCode.FIRST.value().equals(trading.getRoleCode())) {
return 3;
} else if (TradingRoleCode.SECOND.value().equals(trading.getRoleCode())) {
return 1;
} else {
return 2;
}
}, TreeMap::new, Collectors.toList()));