踩坑系列 - BigDecimal
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这种没有值可以转化为二进制的等价数值的时候,就会出现一个近似值的情况
解决方式
有两种解决方式:
- 使用BigDecimal.valueof()方法,构建出一个BigDecimal
- 使用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来替换,大对于精度要求很高的场景,就不得不使用它,但是如果没有掌握正确的打开方式,又会造成反攻影响工期。上述三种场景是是我踩过的坑,记录下来,希望可以帮助到正在面临这类问题的小伙伴们。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!