在金融领域,为了保证数据的精度,往往会使用BigDecimal。本文就来探讨下为什么BigDecimal可以保证精度不丢失。
类介绍
首先来看一下BigDecimal的类声明以及几个属性:
public class BigDecimal extends Number implements Comparable<BigDecimal> {
// 该BigDecimal的未缩放值
private final BigInteger intVal;
// 精度,可以理解成小数点后的位数
private final int scale;
// BigDecimal中的十进制位数,如果位数未知,则为0(备用信息)
private transient int precision;
// Used to store the canonical string representation, if computed.
// 这个我理解就是存实际的BigDecimal值
private transient String stringCache;
// 扩大成long型数值后的值
private final transient long intCompact;
}
从例子入手
通过debug来发现源码中的奥秘是了解类运行机制很好的方式。请看下面的testBigDecimal
方法:
@Test
public void testBigDecimal() {
BigDecimal bigDecimal1 = BigDecimal.valueOf(2.36);
BigDecimal bigDecimal2 = BigDecimal.valueOf(3.5);
BigDecimal resDecimal = bigDecimal1.add(bigDecimal2);
System.out.println(resDecimal);
}
在执行了BigDecimal.valueOf(2.36)
后,查看debug信息可以发现上述提到的几个属性被赋了值:
接下来进到add方法里面,看看它是怎么计算的:
/**
* Returns a BigDecimal whose value is (this + augend),
* and whose scale is max(this.scale(), augend.scale()).
*/
public BigDecimal add(BigDecimal augend) {
if (this.intCompact != INFLATED) {
if ((augend.intCompact != INFLATED)) {
return add(this.intCompact, this.scale, augend.intCompact, augend.scale);
} else {
return add(this.intCompact, this.scale, augend.intVal, augend.scale);
}
} else {
if ((augend.intCompact != INFLATED)) {
return add(augend.intCompact, augend.scale, this.intVal, this.scale);
} else {
return add(this.intVal, this.scale, augend.intVal, augend.scale);
}
}
}
看一下传进来的值:
进入上面代码块第8行的add方法:
private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) {
long sdiff = (long) scale1 - scale2;
if (sdiff == 0) {
return add(xs, ys, scale1);
} else if (sdiff < 0) {
int raise = checkScale(xs,-sdiff);
long scaledX = longMultiplyPowerTen(xs, raise);
if (scaledX != INFLATED) {
return add(scaledX, ys, scale2);
} else {
BigInteger bigsum = bigMultiplyPowerTen(xs,raise).add(ys);
return ((xs^ys)>=0) ? // same sign test
new BigDecimal(bigsum, INFLATED, scale2, 0)
: valueOf(bigsum, scale2, 0);
}
} else {
int raise = checkScale(ys,sdiff);
long scaledY = longMultiplyPowerTen(ys, raise);
if (scaledY != INFLATED) {
return add(xs, scaledY, scale1);
} else {
BigInteger bigsum = bigMultiplyPowerTen(ys,raise).add(xs);
return ((xs^ys)>=0) ?
new BigDecimal(bigsum, INFLATED, scale1, 0)
: valueOf(bigsum, scale1, 0);
}
}
}
这个例子中,该方法传入的参数分别是:xs=236
,scale1=2
,ys=35
,scale2=1
该方法首先计算scale1 - scale2
,根据差值走不同的计算逻辑,这里求出来是1,所以进入到最下面的else代码块(这块是关键):
首先17行校验了一下数值范围
18行将ys扩大了10的n次倍,这里n=raise=1
,所以返回的scaledY=350
接着就进入到20行的add方法:
private static BigDecimal add(long xs, long ys, int scale){
long sum = add(xs, ys);
if (sum!=INFLATED)
return BigDecimal.valueOf(sum, scale);
return new BigDecimal(BigInteger.valueOf(xs).add(ys), scale);
}
这个方法很简单,就是计算和,然后返回BigDecimal对象:
结论
所以可以得出结论:BigDecimal在计算时,实际会把数值扩大10的n次倍,变成一个long型整数进行计算,整数计算时自然可以实现精度不丢失。同时结合精度scale,实现最终结果的计算。
最后说一句(别白嫖,求关注)
陈某每一篇文章都是精心输出,已经写了3个专栏,整理成PDF,获取方式如下:
《Spring Cloud 进阶》PDF:关注公众号:【码猿技术专栏】回复关键词 Spring Cloud 进阶 获取! 《Spring Boot 进阶》PDF:关注公众号:【码猿技术专栏】回复关键词 Spring Boot进阶 获取! 《Mybatis 进阶》PDF:关注公众号:【码猿技术专栏】回复关键词 Mybatis 进阶 获取!
如果这篇文章对你有所帮助,或者有所启发的话,帮忙点赞、在看、转发、收藏,你的支持就是我坚持下去的最大动力!
关注公众号:【码猿技术专栏】,公众号内有超赞的粉丝福利,回复:加群,可以加入技术讨论群,和大家一起讨论技术,吹牛逼!