踩坑系列 - 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这种没有值可以转化为二进制的等价数值的时候,就会出现一个近似值的情况
解决方式
有两种解决方式:

  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来替换,大对于精度要求很高的场景,就不得不使用它,但是如果没有掌握正确的打开方式,又会造成反攻影响工期。上述三种场景是是我踩过的坑,记录下来,希望可以帮助到正在面临这类问题的小伙伴们。

  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@WAT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值