

本章带来的是BigDecimal类的源码解读。BigDecimal类是 Java 在 java.math 包中提供的API类,用来对超过16位有效位的数进行精确的运算。除了复杂度设计和拓展性,里面的数学计算思维也很值得我们学习。对于用惯了float/double的同学,得好好仔细看看了。
JDK官方文档定义
Immutable,arbitrary precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale.
If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).
翻译:BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成。(精度,就是非标度值的数字个数)
如果整数部分为零或正数,则标度是小数点后的位数。
如果为负数,则将该数的非标度值乘以 10 的负scale 次幂。
因此,BigDecimal表示的数值是(unscaledValue × 10的[-scale]次方)。
举个栗子:
BigDecimal表示的数为: unscaledValue × 10的 -scale 次幂,
=1000000000000000020816681711721685132943093776702880859375,scale = 59;
=1000100000000000051159076974727213382720947265625,scale = 46;
-1000100000000000051159076974727213382720947265625,scale = 46;
即是说,
如果scale为零或正数,最终的结果中,小数点后面的位数就等于scale标度
如果 scale 是负数,那最终的结果将会是乘以 10的|scale| 次方
Q
看到这里,有小伙伴要问了,为何输入的0.01,计算机理解到的却是(1000000000000000020816681711721685132943093776702880859375)呢?这里我先挖个坑,文章后面再填。
BigDecimal 的成员变量
这里主要介绍BigDecimal类的全局变量,如下列表:
ONE值1,标度为0。乍一看,都是静态变量,而且都是对某些特殊场景进行的声明。
BigDecimal 的构造器
这里介绍的是BigDecimal的构造方法。一个类需要被使用首先得创建出来吧,那么构造方法就是创建对象时第一个被调用的方法,BigDecimal类的构造器有多种重载实现,抓住关键词:非标度值、 标度、运算规则。
见下列表:
Constructor and Description |
|---|
BigDecimal(BigInteger val)将 BigInteger转换成 BigDecimal 。 |
BigDecimal(BigInteger unscaledVal, int scale)将BigInteger的 BigInteger值和 int等级转换为 BigDecimal 。 |
BigDecimal(BigInteger unscaledVal, int scale, MathContext mc)将 BigInteger未缩放值和 int扩展转换为 BigDecimal ,根据上下文设置进行舍入。 |
BigDecimal(BigInteger val, MathContext mc)根据上下文设置将 BigInteger转换为 BigDecimal舍入。 |
BigDecimal(char[] in)一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造。 |
BigDecimal(char[] in, int offset, int len)一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造,同时允许一个子阵列被指定。 |
BigDecimal(char[] in, int offset, int len, MathContext mc)一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受字符作为的相同序列 BigDecimal(String)构造,同时允许指定一个子阵列和用根据上下文设置进行舍入。 |
BigDecimal(char[] in, MathContext mc)一个转换的字符数组表示 BigDecimal成 BigDecimal ,接受相同的字符序列作为 BigDecimal(String)构造与根据上下文设置进行舍入。 |
BigDecimal(double val)将 double转换为 BigDecimal ,这是 double的二进制浮点值的精确十进制表示。 |
BigDecimal(double val, MathContext mc)将 double转换为 BigDecimal ,根据上下文设置进行舍入。 |
BigDecimal(int val)将 int成 BigDecimal 。 |
BigDecimal(int val, MathContext mc)将 int转换为 BigDecimal ,根据上下文设置进行舍入。 |
BigDecimal(long val)将 long成 BigDecimal 。 |
BigDecimal(long val, MathContext mc)将 long转换为 BigDecimal ,根据上下文设置进行舍入。 |
BigDecimal(String val)将BigDecimal的字符串表示 BigDecimal转换为 BigDecimal 。 |
BigDecimal(String val, MathContext mc)一个转换的字符串表示 BigDecimal成 BigDecimal ,接受相同的字符串作为 BigDecimal(String)构造,利用根据上下文设置进行舍入。 |
粗略扫描一遍之后,我们发现BigDecimal源码设计可谓考虑周全。
BigDecimal 的常用方法
BigDecimal 的常用方法也就是我们小学中学经常用到的加减乘除了,但源码提供的能力可不仅仅局限于此,碍于篇幅,这里只讲述“加减”作为大家阅读源码的突破口,意在抛砖引玉。

/**
* <p>加法</p>
*/
private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) {
// 1 比较两者的标量
long sdiff = (long) scale1 - scale2;
if (sdiff == 0) {
// 2 标量一样可以直接相加运算
return add(xs, ys, scale1);
} else if (sdiff < 0) {
// 3 加数的标量 < 被加数的标量 ,获取标量差异的绝对值(eg:0.1 + 0.01,那么此时的 sdiff 就是 1)
int raise = checkScale(xs,-sdiff);
// 将加数乘以10^n次方,因为其scale标量会设置为跟被加数的scale标量相同
long scaledX = longMultiplyPowerTen(xs, raise);
// 加数扩大之后的结果没有溢出(超过Long类型支持的最大值)
if (scaledX != INFLATED) {
// 相同标量的两者进行相加操作,并返回操作结果(注意返回值是new了一个对象进行存储!!)
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 {
// 4 加数的标量 > 被加数的标量 ,获取标量差异的绝对值(eg:0.01 + 0.1,那么此时的 sdiff 就是 -1)
int raise = checkScale(ys,sdiff);
// 将被加数乘以10^n次方,(n此时为负数),因为其scale标量会设置为跟加数的scale标量相同
long scaledY = longMultiplyPowerTen(ys, raise);
// 被加数扩大之后的结果没有溢出(超过Long类型支持的最大值)
if (scaledY != INFLATED) {
// 相同标量的两者进行相加操作,并返回操作结果(注意返回值是new了一个对象进行存储!!)
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);
}
}
}
/**
* <p>减法,注意运算结果也是封装在新的对象里,千万注意返回值!!</p>
*/
public BigDecimal subtract(BigDecimal subtrahend, MathContext mc) {
// MathContext 的 precision 属性:用于定义操作的位数;,即结果四舍五入到这个精度
if (mc.precision == 0)
// 精度等于0,表示可以直接运算得出结果
return subtract(subtrahend);
// 如果需要四舍五入
return add(subtrahend.negate(), mc);
}
public BigDecimal subtract(BigDecimal subtrahend) {
// 减法的本质就是与一个值相反的数进行相加
if (this.intCompact != INFLATED) {
if ((subtrahend.intCompact != INFLATED)) {
// 取相反值进行加法操作,这里的加法操作跟上文对add的源码解读完全一致!!
return add(this.intCompact, this.scale, -subtrahend.intCompact, subtrahend.scale);
} else {
return add(this.intCompact, this.scale, subtrahend.intVal.negate(), subtrahend.scale);
}
} else {
if ((subtrahend.intCompact != INFLATED)) {
// Pair of subtrahend values given before pair of
// values from this BigDecimal to avoid need for
// method overloading on the specialized add method
return add(-subtrahend.intCompact, subtrahend.scale, this.intVal, this.scale);
} else {
return add(this.intVal, this.scale, subtrahend.intVal.negate(), subtrahend.scale);
}
}
}
public BigDecimal add(BigDecimal augend, MathContext mc) {
// 精度等于0,表示可以直接运算得出结果
if (mc.precision == 0)
return add(augend);
BigDecimal lhs = this;
// If either number is zero then the other number, rounded and
// scaled if necessary, is used as the result.
{
// 精度是否为0
boolean lhsIsZero = lhs.signum() == 0;
boolean augendIsZero = augend.signum() == 0;
if (lhsIsZero || augendIsZero) {
// 减数与被减数之一的精度为0
int preferredScale = Math.max(lhs.scale(), augend.scale());
BigDecimal result;
if (lhsIsZero && augendIsZero)
return zeroValueOf(preferredScale);
// 减数精度为0,那么返回被减数四舍五入的结果,反之减数四舍五入结果
result = lhsIsZero ? doRound(augend, mc) : doRound(lhs, mc);
if (result.scale() == preferredScale)
return result;
else if (result.scale() > preferredScale) {
return stripZerosToMatchScale(result.intVal, result.intCompact, result.scale, preferredScale);
} else { // result.scale < preferredScale
int precisionDiff = mc.precision - result.precision();
int scaleDiff = preferredScale - result.scale();
if (precisionDiff >= scaleDiff)
return result.setScale(preferredScale); // can achieve target scale
else
return result.setScale(result.scale() + precisionDiff);
}
}
}
// 减数与被减数的精度差
long padding = (long) lhs.scale - augend.scale;
if (padding != 0) { // scales differ; alignment needed
// 两者精度差不为0,需要对两者的,返回值时一个BigDecimal数组,第一个元素是精度较大者
BigDecimal arg[] = preAlign(lhs, augend, padding, mc);
// 设置减数与被减数的标量一致
matchScale(arg);
lhs = arg[0];
augend = arg[1];
}
// 运算结果
return doRound(lhs.inflated().add(augend.inflated()), lhs.scale, mc);
}
针对 BigDecimal 常用方法的测试用例
基于上面的分析,我们对常用方法提供测试用例了解基础使用:
public class TestBigDecimal {
public static void main(String[] args) {
test1();
}
private static void test1(){
BigDecimal valueSec = new BigDecimal(1000000);
BigDecimal valueFir = new BigDecimal(1000000);
BigDecimal valueThi = new BigDecimal(-1000000);
//尽量用字符串的形式初始化
BigDecimal stringFir = new BigDecimal("0.005");
BigDecimal stringSec = new BigDecimal("1000000");
BigDecimal stringThi = new BigDecimal("-1000000");
//加法
BigDecimal addVal = valueFir.add(valueSec);
System.out.println("加法用value结果:" + addVal);
BigDecimal addStr = stringFir.add(stringSec);
System.out.println("加法用string结果:" + addStr);
//减法
BigDecimal subtractVal = valueFir.subtract(valueSec);
System.out.println("减法value结果:" + subtractVal);
BigDecimal subtractStr = stringFir.subtract(stringSec);
System.out.println("减法用string结果:" + subtractStr);
//乘法
BigDecimal multiplyVal = valueFir.multiply(valueSec);
System.out.println("乘法用value结果:" + multiplyVal);
BigDecimal multiplyStr = stringFir.multiply(stringSec);
System.out.println("乘法用string结果:" + multiplyStr);
//绝对值
BigDecimal absVal = valueThi.abs();
System.out.println("绝对值用value结果:" + absVal);
BigDecimal absStr = stringThi.abs();
System.out.println("绝对值用string结果:" + absStr);
//除法
BigDecimal divideVal = valueSec.divide(valueFir, 20, BigDecimal.ROUND_HALF_UP);
System.out.println("除法用value结果:" + divideVal);
BigDecimal divideStr = stringSec.divide(stringFir, 20, BigDecimal.ROUND_HALF_UP);
System.out.println("除法用string结果:" + divideStr);
}
}
输出结果:
加法用value结果:2000000
加法用string结果:1000000.005
减法value结果:0
减法用string结果:-999999.995
乘法用value结果:1000000000000
乘法用string结果:5000.000
绝对值用value结果:1000000
绝对值用string结果:1000000
除法用value结果:1.00000000000000000000
除法用string结果:200000000.00000000000000000000BigDecimal 和 基础数据类型的使用区别?
问
文章一开头就有小伙伴要问了,为何输入的0.01,计算机理解到的却是
1000000000000000020816681711721685132943093776702880859375呢?
答
Good,那么这里回答下小伙伴的疑问。
热心老铁
float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。商业计算往往要求结果精确(银行/金融/风控/支付结算等)就必须选择BigDecimal了。
话虽如此,使用BigDecimal还是有两个缺点的哦。一是与使用基本数据类型相比,BigDecimal的使用不方便,二是BigDecimal对代码性能造成一定的影响。使用BigDecimal的一个好处是,它允许你完全控制舍入,每当一个操作涉及舍入时,它允许你从8种舍入模式中选择。 So,如果性能非常关键,而且你又不介意自己记录十进制小数点,而且所涉及到的数值不大,可以使用int或者long。但如果数值可能超过了18位数字,就必须使用BigDecimal了。
以上是这次分享的 BigDecimal 源码剖析的全部内容了
—END—