BigDecimal 常用计算方法与注意事项

BigDecimal 是 Java 提供的一个关于小数精确计算的类,其位于java.math包下。不同于基本数据类型,BigDecimal是调用相关方法来进行运算。因为其拥有非常精确的小数计算能力,所以比较适合用于财务相关的计算等等。但是,其运行效率是不及 FloatDouble 的,要按照实际情况,运用在合理的地方。

对象初始化

BigDecimal bigDec = new BigDecimal("99.6");
//不建议使用
BigDecimal bigDec = new BigDecimal(99.6);
强烈建议使用 String 类型进行初始化对象,强烈不推荐使用 Double 类型。原因何在?我们来看一个小测试。 执行下方代码
BigDecimal bigDecimal=new BigDecimal("99.6");
System.out.println(bigDecimal.toString());
运行结果
99.6
而执行下方代码
BigDecimal bigDecimal=new BigDecimal(99.6);
System.out.println(bigDecimal.toString());
运行结果
99.599999999999994315658113919198513031005859375
问题立刻显现出来,使用 Double 型数据初始化 Bigdecimal 是不精确的,会损失精度。这不是 BigDecimal 的问题,而属于 Double 本身。在 IDEA 中,使用 Double 初始化时,则会“报黄”警告。所以请务必使用 String 初始化 BigDecimal !IDEA 警告

建议的数据库解决方案

基于上一部分得到的结论,建议在数据库中使用VARCHAR 类型或者 DECIMAL,这里以 MySQL + MyBatis 为例。
数据库数据类型MyBatis 实体类数据类型
VARCHARString
DECIMALBigDecimal

加减运算

加减运算比较简单,使用 add()subtract() 方法进行处理。

减法

示例代码
BigDecimal bigDecimal = new BigDecimal("99.6");
BigDecimal subtract = bigDecimal.subtract(new BigDecimal("66.9"));
System.out.println(subtract.toString());
System.out.println(bigDecimal.toString());
运行结果
32.7
99.6
此处需要注意的是,所有的运算方法在执行后并不会影响参与计算的数,他只会将计算结果返回! 你可以使用新的对象来接受,或者覆盖旧的对象。 示例代码
BigDecimal bigDecimal = new BigDecimal("99.6");
bigDecimal = bigDecimal.subtract(new BigDecimal("66.9"));
System.out.println(bigDecimal.toString());

加法

示例代码
BigDecimal bigDecimal = new BigDecimal("99.6");
bigDecimal = bigDecimal.add(new BigDecimal("66.9"));
System.out.println(bigDecimal.toString());
运行结果
166.5

乘除运算

乘法

使用 multiply()方法进行处理。 示例代码
BigDecimal bigDecimal = new BigDecimal("99.6");
bigDecimal = bigDecimal.multiply(new BigDecimal("66.9"));
System.out.println(bigDecimal.toString());
运行结果
6663.24

除法

除法是比较特殊的一个运算,因为除数与被除数的关系,可能会造成结果为无限循环小数无限不循环小数,为了解决这一问题你需要指定小数保留方案
示例代码
BigDecimal bigDecimal = new BigDecimal("99.6");
bigDecimal = bigDecimal.divide(new BigDecimal("3"));
System.out.println(bigDecimal.toString());
运行结果
33.2
因为该运算结果不是无限循环小数,所以一切看起来都很 Nice ,但是当你稍稍改动一下,它就会“原形毕露”。
BigDecimal bigDecimal = new BigDecimal("99.6");
bigDecimal = bigDecimal.divide(new BigDecimal("66.9"));
System.out.println(bigDecimal.toString());
运行结果 Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide(BigDecimal.java:1690) at ... 我是谁?我在哪?我写的是什么BUG?我们来看关键字Non-terminating decimal expansion,翻译过来就是无穷小数扩张。看到这里,你是否已经明白问题所在。也就是上边所提到的,需要指定小数保留方案。 改动一下
BigDecimal bigDecimal = new BigDecimal("99.6");
bigDecimal = bigDecimal.divide(new BigDecimal("66.9"),2,BigDecimal.ROUND_DOWN);
System.out.println(bigDecimal.toString());
运行结果
1.48
此时一切都已恢复往日的平静,那么到底是什么力量遏制住了可恶的 Exception 呢?细心的同学会发现在 divide()方法中多了两个参数,一个是2,一个是BigDecimal.ROUND_DOWN。其中,2是保留小数的位数,这个很容易理解。而BigDecimal.ROUND_DOWN又是什么东西呢?他是 BigDecimal 类中的小数保留方案常量,算上这一个,一共有八个方案常量,请看解析。
常量描述
BigDecimal.ROUND_UP舍入远离零的舍入模式。在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。
BigDecimal.ROUND_DOWN接近零的舍入模式。在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。
BigDecimal.ROUND_CEILING接近正无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;如果为负,则舍入行为与 ROUND_DOWN 相同。
BigDecimal.ROUND_FLOOR接近负无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;如果为负,则舍入行为与 ROUND_UP 相同。
BigDecimal.ROUND_HALF_UP向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。注意,这是我们大多数人在小学时就学过的舍入模式(四舍五入)。
BigDecimal.ROUND_HALF_DOWN向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。
BigDecimal.ROUND_HALF_EVE向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。此舍入模式也称为“银行家舍入法”,主要在美国使用。
BigDecimal.ROUND_UNNECESSARY断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。
这么多的小数保留方案,其实常用的也不多,感兴趣的话,可以自己写个测试类玩玩。

比较大小

比较大小也是需要调用方法的,使用compareTo()方法即可处理。
示例代码
BigDecimal a = new BigDecimal("99.6");
BigDecimal b = new BigDecimal("66.9");
int i = a.compareTo(b);
System.out.println(i);
运行结果
1
compareTo()方法的返回值是一个int型的数据,以上方示例为例,请看解析
情况compareTo() 的返回值
a>b1
a=b0
a<b-1
当你要判断一个数的与0的关系(正负)时可以这样写
BigDecimal a = new BigDecimal("-99.6");
if (a.compareTo(BigDecimal.ZERO) > 0) {
	//a是正数
}else if (a.compareTo(BigDecimal.ZERO) == 0){
	//a==0
}else {
	//a是负数
}

其他

去除无用的零

示例代码
BigDecimal a = new BigDecimal("99.12300000");
BigDecimal b = a.stripTrailingZeros();
System.out.println(b.toString());
运行结果
99.123
更多技术文章请访问我的个人网站:zyan1226.cn