BigDecimal需要避免的一些坑

为什么要用BigDecimal?

因为float和double是不精确的,在进行一些金额相关的计算时,对准确度要求较高,因此要使用BigDecimal。

为什么float和double是不精确的呢?

这要从十进制转二进制讲起,整数转为二进制,采用的是"除2取余,倒序排列"。比如10转为二进制是1010,计算如下:

10 / 2 = 5 余 0

5 / 2 = 2 余 1

2 / 2 = 1 余 0

1 / 2 = 0 余 1

当整数部分变为0时停止计算,此时取余数的倒叙排列,则为1010。

而小数转为二进制,采用的是"乘2取整,正序排列"。比如0.625变为小数是0.101,计算如下:

0.625 * 2 = 1.25 --取出整数部分 1

0.25 * 2 = 0.5 --取出整数部分 0

0.5 * 2 = 1 --取出整数部分 1

没有小数时停止计算,此时,取整数的正序排列,则为 0.101。

那么所有小数都能精确的转为二进制吗?

并不是,比如0.1要转为二进制,我们计算一下:

0.1 * 2 = 0.2 --取出整数部分 0

0.2 * 2 = 0.4 --取出整数部分 0

0.4 * 2 = 0.8 --取出整数部分 0

0.8 * 2 = 1.6 --取出整数部分 1

0.6 * 2 = 1.2 --取出整数部分 1

0.2 * 2 = 0.4 --取出整数部分 0

······

到这里就发现了,0.1 转为二进制是0.000110......,会一直循环下去,所以说float和double是不精确的。

为什么BigDecimal是精确的?

BigDecimal是由无标度值和标度两部分组成,比如 3.12 在 BigDecimal内部其实形式为 (312,2),312是无标度值,而2是标度,也就是小数位数。很明显 312 是整数,可以精确的转为二进制,因此BigDecimal是精确的。

BigDecimal要避免的一些坑

那么既然BigDecimal是精确的,是不是就可以随意使用呢?实际上BigDecimal也有一些需要注意的地方,否则就会掉坑里了。

BigDecimal的创建

有以下几种创建方式

arduino 复制代码
public BigDecimal(double val);
public BigDecimal(long val);
public BigDecimal(String val);

BigDecimal.valueOf(double val);
BigDecimal.valueOf(long val);

那么采用这几种方式创建一个0.1,看下结果会有什么不同?

csharp 复制代码
public static void testDecimal() {

    BigDecimal a = new BigDecimal(0.1);

    BigDecimal b = new BigDecimal("0.1");

    BigDecimal c = BigDecimal.valueOf(0.1);

    System.out.println("a = " + a);
    System.out.println("b = " + b);
    System.out.println("c = " + c);
}

结果为:
a = 0.1000000000000000055511151231257827021181583404541015625
b = 0.1
c = 0.1

有人就疑惑了,上面不是刚说到BigDecimal是精确的吗,怎么这里通过new BigDecimal(double val)方式创建的就不是精确的了。这是因为传进去的0.1是个double值,而double转为二进制后是不精确的,实际上传的值是new BigDecimal(0.10000000000000000555111...) ,因此BigDecimal也就不再是0.1了。

为什么BigDecimal.valueOf(0.1)方式传进的也是double值,是精确的呢,我们看下源码

Double.toString(0.10000000000000000555111...),经过计算,最后会变为字符串0.1,因此BigDecimal也就为0.1了。

因此对于double转BigDecimal,要采用 new BigDecimal(String val) 方式或者BigDecimal.valueOf() 方式,不能使用new BigDecimal(double val)方式。

BigDecimal加减乘除运算

加减乘除计算

csharp 复制代码
public static void testDecimal2() {
    BigDecimal a = new BigDecimal("2.5");
    BigDecimal b = new BigDecimal("2.0");
    BigDecimal c = new BigDecimal("1.5");

    System.out.println("a + b = " + a.add(b));
    System.out.println("a - b = " + a.subtract(b));
    System.out.println("a * b = " + a.multiply(b));
    System.out.println("a / b = " + a.divide(b));
}

计算结果如下

如果计算 a / c 会怎样呢,我们试下

csharp 复制代码
System.out.println("a / c = " + a.divide(c));

结果是

这个错误告诉我们 a / c 除不尽,所以报错了。正确写法应该如下所示:

csharp 复制代码
System.out.println("a / c = " + a.divide(c,2, RoundingMode.HALF_UP));

2代表保留2位小数,RoundingMode.HALF_UP代表是四舍五入,和BigDecimal.ROUND_HALF_UP是一样的。RoundingMode除了HALF_UP模式,还有很多类型,比如UP模式,进入RoundingMode.UP看看注释,就能清楚的理解其含义

多项计算用法:

css 复制代码
System.out.println("a / c * b = " + a.divide(c,10, RoundingMode.HALF_UP).multiply(b).setScale(2,RoundingMode.HALF_UP));

a / c 时保留10位小数,是为了中间的值更加精确,然后乘以 b ,最后再通过setScale(2,RoundingMode.HALF_UP) 四舍五入保留2位小数。

BigDecimal比较大小

看下equals和compareTo两个方法有什么区别

csharp 复制代码
public static void testDecimal3() {
    BigDecimal a = new BigDecimal("1.0");
    BigDecimal b = new BigDecimal("1.00");

    System.out.println("a.equals(b) = " + a.equals(b));

    //compareTo  0为相等,1为大于,-1是小于
    boolean value = a.compareTo(b) == 0 ? true : false;

    System.out.println("a.compareTo(b) = " + value);

}

结果如下:

如果前面的内容认真看了,这里应该能大致猜到。BigDecimal("1.0") 实际内部的形式是 (10,1),而BigDecimal("1.00")实际内部形式是(100,2),它们两个的无标度值和标度都不同,所以通过equals比较是不等的。比较大小应该用compareTo方法去比较。

总结

1.在涉及金额计算等精确度要求较高的场景,应该使用BigDecimal。

2.BigDecimal创建对象时应该使用字符串构造器方法或者BigDecimal.valueOf()方法。

3.BigDecimal在复杂计算时,除法计算要带上精度,最后的值也要设置精度。

4.BigDecimal比较大小时应采用compareTo()方法比较

相关推荐
追逐时光者几秒前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
后端·.net
程序员飞哥6 分钟前
90后大龄程序员失业4个月终于上岸了
后端·面试·程序员
zs宝来了40 分钟前
Playwright 自动发布 CSDN 的完整实践
java
吴声子夜歌2 小时前
TypeScript——基础类型(三)
java·linux·typescript
GetcharZp2 小时前
Git 命令行太痛苦?这款 75k Star 的神级工具,让你告别“合并冲突”恐惧症!
后端
Victor3563 小时前
MongoDB(69)如何进行增量备份?
后端
Victor3563 小时前
MongoDB(70)如何使用副本集进行备份?
后端
DynamicsAgg3 小时前
企业数字化底座-k8s企业实践系列第二篇pod创建调度
java·容器·kubernetes
千寻girling3 小时前
面试官 : “ 说一下 Python 中的常用的 字符串和数组 的 方法有哪些 ? ”
人工智能·后端·python