BigDecimal

用于精确的数值计算,无需担心小数精度所带来的问题。

继承与实现

  • 继承Number抽象类
    • 强制实现6种基本类型(除了boolean和char)的返回。
    • 实现java.io.Serializable接口
  • 实现Comparable接口

关键属性

BigDecimal 内部由long类型+小数点位数来表示数值的。

1
2
3
4
private final BigInteger intVal;//大于18位,long类型10进制最多19位。用于存放去掉小数点的数值
private final transient long intCompact;//小于等于18位,用于存放去掉小数点的数值
private final int scale;//小数位数
private transient int precision;//总位数

初始化,构造

由于folat和double无法精确表示小数,所以在用BigDecimal初始化的时候,也不选择用folat和double。
一般正常用的都是String。
MathContext是用于精确位数和入舍操作。
这里给出用String初始化的方法,如想看其他方法,可以自行翻看源码,处理方式大同小异。

public BigDecimal(String val)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//传入String进行初始化
public BigDecimal(String val) {
this(val.toCharArray(), 0, val.length());//String转成char数组,传入方法
}

//offset 起始位置,len char[]的长度
public BigDecimal(char[] in, int offset, int len) {
this(in,offset,len,MathContext.UNLIMITED);//MathContext.UNLIMITED 无限制,默认位数为0,向上四舍五入
}

public BigDecimal(char[] in, int offset, int len, MathContext mc) {
if (offset + len > in.length || offset < 0)//校验offset
throw new NumberFormatException("Bad offset or len arguments for char[] input.");
int prec = 0;//precision临时变量
int scl = 0;//scale临时变量
long rs = 0;//intCompact临时变量
BigInteger rb = null;//intVal临时变量
try {
boolean isneg = false;//正负标志
if (in[offset] == '-') {
isneg = true;
offset++;
len--;
} else if (in[offset] == '+') {
offset++;
len--;
}
boolean dot = false;//小数标志
long exp = 0;
char c;
boolean isCompact = (len <= MAX_COMPACT_DIGITS);//长度18
int idx = 0;
if (isCompact) {
for (; len > 0; offset++, len--) {//遍历char数组
c = in[offset];
if ((c == '0')) {//是0
if (prec == 0)
prec = 1;
else if (rs != 0) {
rs *= 10;
++prec;
}
if (dot)
++scl;
} else if ((c >= '1' && c <= '9')) {//是数字
int digit = c - '0';//获取绝对数值,不是字符类型的数值
if (prec != 1 || rs != 0)
++prec;
rs = rs * 10 + digit;//赋值
if (dot)
++scl;
} else if (c == '.') {//是点
if (dot)//不能是多个点,只能一个
throw new NumberFormatException();
dot = true;
} else if (Character.isDigit(c)) {//应该是历史版本里留下的,当前版本用不到这个判断,英文注释是slow path,判断是不是数字
int digit = Character.digit(c, 10);
if (digit == 0) {
if (prec == 0)
prec = 1;
else if (rs != 0) {
rs *= 10;
++prec;
}
} else {
if (prec != 1 || rs != 0)
++prec;
rs = rs * 10 + digit;
}
if (dot)
++scl;
} else if ((c == 'e') || (c == 'E')) {//正则表达式
exp = parseExp(in, offset, len);
if ((int) exp != exp) // overflow
throw new NumberFormatException();
break;
} else {
throw new NumberFormatException();
}
}
if (prec == 0)
throw new NumberFormatException();
if (exp != 0) {
scl = adjustScale(scl, exp);
}
rs = isneg ? -rs : rs;//处理正负
int mcp = mc.precision;//获取处理位数
int drop = prec - mcp;//删减的位数
if (mcp > 0 && drop > 0) {//需要删减位数的话,并进行进位入舍
while (drop > 0) {
scl = checkScaleNonZero((long) scl - drop);
rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
prec = longDigitLength(rs);
drop = prec - mcp;
}
}
} else {//大于18位的字符串,这里的处理和上方一样,只是缩减了代码。增加了br的赋值,rs为long的最小值
char coeff[] = new char[len];
for (; len > 0; offset++, len--) {
c = in[offset];
if ((c >= '0' && c <= '9') || Character.isDigit(c)) {
if (c == '0' || Character.digit(c, 10) == 0) {
if (prec == 0) {
coeff[idx] = c;
prec = 1;
} else if (idx != 0) {
coeff[idx++] = c;
++prec;
}
} else {
if (prec != 1 || idx != 0)
++prec;
coeff[idx++] = c;
}
if (dot)
++scl;
continue;
}
if (c == '.') {
// have dot
if (dot)
throw new NumberFormatException();
dot = true;
continue;
}
if ((c != 'e') && (c != 'E'))
throw new NumberFormatException();
exp = parseExp(in, offset, len);
if ((int) exp != exp) // overflow
throw new NumberFormatException();
break;
}
if (prec == 0)
throw new NumberFormatException();
if (exp != 0) {
scl = adjustScale(scl, exp);
}
rb = new BigInteger(coeff, isneg ? -1 : 1, prec);
rs = compactValFor(rb);
int mcp = mc.precision;
if (mcp > 0 && (prec > mcp)) {
if (rs == INFLATED) {
int drop = prec - mcp;
while (drop > 0) {
scl = checkScaleNonZero((long) scl - drop);
rb = divideAndRoundByTenPow(rb, drop, mc.roundingMode.oldMode);
rs = compactValFor(rb);
if (rs != INFLATED) {
prec = longDigitLength(rs);
break;
}
prec = bigDigitLength(rb);
drop = prec - mcp;
}
}
if (rs != INFLATED) {
int drop = prec - mcp;
while (drop > 0) {
scl = checkScaleNonZero((long) scl - drop);
rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
prec = longDigitLength(rs);
drop = prec - mcp;
}
rb = null;
}
}
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new NumberFormatException();
} catch (NegativeArraySizeException e) {
throw new NumberFormatException();
}
this.scale = scl;
this.precision = prec;
this.intCompact = rs;
this.intVal = rb;
}

常用方法

内部方法太多了,判断处理也很多,还有对最终值的位数和18做比较,所以这里就不详细展开分析了。有想看的同学,请自行翻阅源码。

add

加法可以当减法用,所以内部逻辑有越位和回位处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public BigDecimal add(BigDecimal augend) {
if (this.intCompact != INFLATED) {//被加数小于等于18位
if ((augend.intCompact != INFLATED)) {//加数小于等于18位
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);
}
}
}

subtract

看到了吧,内部是add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public BigDecimal subtract(BigDecimal subtrahend) {
if (this.intCompact != INFLATED) {
if ((subtrahend.intCompact != INFLATED)) {
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)) {
return add(-subtrahend.intCompact, subtrahend.scale, this.intVal, this.scale);
} else {
return add(this.intVal, this.scale, subtrahend.intVal.negate(), subtrahend.scale);
}
}
}

multiply

乘法可以不设置位数和入舍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public BigDecimal multiply(BigDecimal multiplicand) {
int productScale = checkScale((long) scale + multiplicand.scale);//算出最终的小数点位数
if (this.intCompact != INFLATED) {//被加数小于等于18位
if ((multiplicand.intCompact != INFLATED)) {//加数小于等于18位
return multiply(this.intCompact, multiplicand.intCompact, productScale);
} else {
return multiply(this.intCompact, multiplicand.intVal, productScale);
}
} else {
if ((multiplicand.intCompact != INFLATED)) {
return multiply(multiplicand.intCompact, this.intVal, productScale);
} else {
return multiply(this.intVal, multiplicand.intVal, productScale);
}
}
}

divide

除法也有直接除的方法,但是入舍和位数都是默认的,在实际应用中,不符合数据的精度。
一般而言,会使用divide(BigDecimal divisor, int scale, int roundingMode),手动设置入舍和小数点的精度。
大体流程先根据小数点的位数,除数和被除数进行扩容,还是有18位限制,然后直接做除法(/),之后在做小数点位数的处理和入舍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public BigDecimal divide(BigDecimal divisor) {
if (divisor.signum() == 0) { // x/0
if (this.signum() == 0) // 0/0
throw new ArithmeticException("Division undefined"); // NaN
throw new ArithmeticException("Division by zero");
}
int preferredScale = saturateLong((long) this.scale - divisor.scale);
if (this.signum() == 0) // 0/y
return zeroValueOf(preferredScale);
else {
MathContext mc = new MathContext( (int)Math.min(this.precision() +
(long)Math.ceil(10.0*divisor.precision()/3.0),
Integer.MAX_VALUE),
RoundingMode.UNNECESSARY);
BigDecimal quotient;
try {
quotient = this.divide(divisor, mc);
} catch (ArithmeticException e) {
throw new ArithmeticException("Non-terminating decimal expansion; " +
"no exact representable decimal result.");
}
int quotientScale = quotient.scale();
if (preferredScale > quotientScale)
return quotient.setScale(preferredScale, ROUND_UNNECESSARY);
return quotient;
}
}
1
2
3
public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode) {
return this.divide(divisor, scale, roundingMode.oldMode);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) {
if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY)
throw new IllegalArgumentException("Invalid rounding mode");
if (this.intCompact != INFLATED) {
if ((divisor.intCompact != INFLATED)) {
return divide(this.intCompact, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
} else {
return divide(this.intCompact, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
}
} else {
if ((divisor.intCompact != INFLATED)) {
return divide(this.intVal, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
} else {
return divide(this.intVal, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
}
}
}

获取值

有unscaledValue(),intValue(),longValue(),floatValue(),doubleValue()。位数超长的,会变成科学计数法。toString()也会变,可以用toPlainString()。