踩坑系列 - BigDecimal

2024-01-02 09:49:15

BigDecimal引发的血案:

从事金融开发已经有快一年了,最近开发一个关于汇率和利息计算的业务,发现在使用BigDecimal的时候,遇到了几个很抓马的BUG,为了避免再犯,所以写了这个系列的文章记录一下,主要是记录工作期间(金融行业)遇到的一些坑,今天是第一篇,以后遇到的坑都会记录在此系列里面,还望各位大佬指点,也希望能帮助到一些小伙伴。

场景一、BigDecimal实例化造成的数值错误

问题场景代码

/**
* 计算利息Service
*/
public class CountInterestServiceImple implements ICountInterestService {
	public void countInterest() {
		// 计算利息
		BigDecimal rate = new BigDecimal(0.01);
		System.out.println("rate = " + rate );
		// 业务逻辑
		// ...
	}
}

在这里插入图片描述
通过输出发现这里的rate期望值应该是0.01,但是实际上却是上图所示的值。

原因
是因为BigDecimal在new的时候,会将double类型的参数转化为二进制的等价字节,然后存入BigDecimal中,但是对于想0.01这种没有值可以转化为二进制的等价数值的时候,就会出现一个近似值的情况
解决方式
有两种解决方式:

  1. 使用BigDecimal.valueof()方法,构建出一个BigDecimal
  2. 使用new BigDecimal(“”),直接绕过二进制转化,直接使用String,底层double.valueof转化。
BigDecimal bigDecimal2 = BigDecimal.valueOf(0.01);
BigDecimal bigDecimal3 = new BigDecimal("12345678456734535675.9543245347543475");
System.out.println("bigDecimal2 = " + bigDecimal2);
System.out.println("bigDecimal3 = " + bigDecimal3);

在这里插入图片描述
源码分析
BigDecimal.valueOf()底层就是通过Double.toString(val)将其转为字符串存入BigDecimal里面的,所以归根到底,最直接的方式就是使用new BigDecimal(“”)的方式构建BigDecimal

public static BigDecimal valueOf(double val) {
     // Reminder: a zero double returns '0.0', so we cannot fastpath
     // to use the constant ZERO.  This might be important enough to
     // justify a factory approach, a cache, or a few private
     // constants, later.
     return new BigDecimal(Double.toString(val));
}

public BigDecimal(String val) {
    this(val.toCharArray(), 0, val.length());
}

场景二、BigDecimal比较大小

问题代码复现

public class CountInterestServiceImple implements ICountInterestService {
	public void countInterest() {
		// 比较大小
		BigDecimal bigDecimal1 = new BigDecimal("0.01");
		BigDecimal bigDecimal2 = new BigDecimal("0.010");
		System.out.println(bigDecimal1.equals(bigDecimal2));
		// 业务逻辑
		// ...
	}
}

在这里插入图片描述
通过输出发现这里打印的是false,与期望的true并不符。

原因
是因为在使用equals比较大小的时候,BigDecimal里重写了Object的equals方法,不仅比较了数值大小,还比较了数值精度,也就是小数位数。0.01是两位小数,而0.010是三位小数,所以返回的是false。

解决方式
使用CompareTo方法比较大小,有三种返回值

  • -1:表示前面的数小于后面的数
  • 0:表示两数相等
  • 1:表示前面的数大于后面的数
BigDecimal bigDecimal1 = new BigDecimal("0.01");
BigDecimal bigDecimal2 = new BigDecimal("0.010");
System.out.println(bigDecimal1.compareTo(bigDecimal2));

在这里插入图片描述

场景三、BigDecimal做运算

bigDecimal在计算的时候,如果计算出来是一个无限小数,则会报错,需要考虑保留的小数位和保留模式

问题代码复现

public class CountInterestServiceImple implements ICountInterestService {
	public void countInterest() {
		// 计算
		BigDecimal bigDecimal1 = new BigDecimal("1");
		BigDecimal bigDecimal2 = new BigDecimal("3");
		BigDecimal bigDecimal3 = bigDecimal1.divide(bigDecimal2); // 0.3333...
		System.out.println(bigDecimal3);
		// 业务逻辑
		// ...
	}
}

在这里插入图片描述
可以看到打印抛出一个Non-terminating decimal Execption的错误,意思就是无线不循环小数,无法使用BigDecimal表示。

解决方式
使用BigDecimal的带有小数精度和保留模式的构造函数,指定计算后小数的保留规则

BigDecimal bigDecimal1 = new BigDecimal("1");
BigDecimal bigDecimal2 = new BigDecimal("3");
BigDecimal bigDecimal3 = bigDecimal1.divide(bigDecimal2,2,RoundingMode.HALF_UP); // 0.33
System.out.println(bigDecimal3);

在这里插入图片描述

小结

在金融行业里,BigDecimal的使用频率必然少不了,有的时候可以用Double来替换,大对于精度要求很高的场景,就不得不使用它,但是如果没有掌握正确的打开方式,又会造成反攻影响工期。上述三种场景是是我踩过的坑,记录下来,希望可以帮助到正在面临这类问题的小伙伴们。

文章来源:https://blog.csdn.net/mmklo/article/details/135330596
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。