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()方法比较

相关推荐
是小崔啊5 分钟前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
黄公子学安全14 分钟前
Java的基础概念(一)
java·开发语言·python
liwulin050615 分钟前
【JAVA】Tesseract-OCR截图屏幕指定区域识别0.4.2
java·开发语言·ocr
jackiendsc20 分钟前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
Yuan_o_20 分钟前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
Oneforlove_twoforjob24 分钟前
【Java基础面试题027】Java的StringBuilder是怎么实现的?
java·开发语言
程序员一诺1 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
数据小小爬虫1 小时前
利用Java爬虫获取苏宁易购商品详情
java·开发语言·爬虫
小汤猿人类1 小时前
nacos-服务发现注册
java·开发语言·服务发现
码农爱java1 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式