为什么使用BigDecimal

首先看看floatdouble在java中的运算:

1
2
3
4
5
6
public static void main(String[] args) {
System.out.println(0.06+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(303.1/1000);
}

运行结果如下:

0.06999999999999999
0.5800000000000001
401.49999999999994
0.30310000000000004

原因在于我们的计算机是二进制的.浮点数没有办法是用二进制进行精确表示.我们的CPU表示浮点数由两个部分组成:指数和尾数,这样的表示方法一般都会失去一定的精确度,有些浮点数运算也会产生一定的误差.如:2.4的二进制表示并非就是精确的2.4.反而最为接近的二进制表示是 2.3999999999999999.浮点数的值实际上是由一个特定的数学公式计算得到的.
其实java的float只能用来进行科学计算或工程计算,在大多数的商业计算中,一般采用java.math.BigDecimal类来进行精确计算.
借用《Effactive Java》这本书中的话:

float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候BigDecimal就派上大用场啦。

创建BigDecimal

一般可以通过BigDecimal提供的valueOf静态方法或者构造函数来创建:

1
2
BigDecimal b1 = new BigDecimal(Double.toString(0.48));
BigDecimal b2 = BigDecimal.valueOf(0.48);

来详细看看常用的参数类型为double以及string的构造函数:

1
2
3
4
BigDecimal aDouble = new BigDecimal(1.22);
System.out.println("construct with a double value: " + aDouble);
BigDecimal aString = new BigDecimal("1.22");
System.out.println("construct with a String value: " + aString);

输出的结果:

construct with a doublevalue:1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22

参数类型为string的构造函数输出为我们预想的值,但double输出了一个1.2199999999999999733546474089962430298328399658203125.
来看看jdk的描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* .......
* <p>
* <b>Notes:</b>
* <ol>
* <li>
* The results of this constructor can be somewhat unpredictable.
* One might assume that writing {@code new BigDecimal(0.1)} in
* Java creates a {@code BigDecimal} which is exactly equal to
* 0.1 (an unscaled value of 1, with a scale of 1), but it is
* actually equal to
* 0.1000000000000000055511151231257827021181583404541015625.
* This is because 0.1 cannot be represented exactly as a
* {@code double} (or, for that matter, as a binary fraction of
* any finite length). Thus, the value that is being passed
* <i>in</i> to the constructor is not exactly equal to 0.1,
* appearances notwithstanding.
*
* ......
*/

  1. 参数类型为double的构造方法的结果有一定的不可预知性.有人可能认为在Java中写入new BigDecimal(0.1)所创建的BigDecimal正好等于0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625.这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数).这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值.
  2. 另一方面,String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1.因此,比较而言,通常建议优先使用String构造方法.
  3. 如果必须使用double来作为构造函数,使用Double.toString(double)方法,然后使用new BigDecimal(doubelStr)也能获取想要的结果.或者使用BigDecimal.valueOf()

基本操作

加减乘除

基本的加减乘除方法:

1
2
3
4
BigDecimal add(BigDecimal val) //BigDecimal 加法
BigDecimal subtract (BigDecimal val) //BigDecimal 减法
BigDecimal multiply (BigDecimal val) //BigDecimal 乘法
BigDecimal divide (BigDecimal val,RoundingMode mode) 除法

除法详解

1
a.divide(b);

注意以上相除很有可能,如果出现除不尽的情况,比如10 / 3 = 3.333333333333.... 则会抛出 java.lang.ArithmeticException的异常.
这个时候可以指定scale(精度)以及roundingMode(舍入模式).

1
a.divide(b,int scale,RoundingMode mode);

第二个参数: 需要精确到的位数(小数点后需要保留的位数)
第三个参数: 舍入模式,具体有哪些舍入模式:戳这里

设置精度(小数位数)

1
2
BigDecimal.setScale(int scale)
BigDecimal.setScale(int scale,RoundingMode mode)

不推荐使用第一个只带有一个scale参数的方法,没有任何意义,比如调用setScale(2),如果这个BigDecimal有三位小数,则会直接抛异常.
如果位数不够,则会加0填充,new BigDecimal("3").setScale(2) -> 3.00
第一个参数: 需要精确到的位数(小数点后需要保留的位数)
第二个参数: 舍入模式,具体有哪些舍入模式:戳这里

舍入模式

java.math中提供有一个枚举类RoundingMode,前几个是比较常用的:

  • HALF_UP 向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6(四舍五入,5入)
  • HALF_DOWN 向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5(四舍五入,5舍掉)
  • UP 远离零方向舍入的舍入模式.始终对非零舍弃部分前面的数字加 1.注意,此舍入模式始终不会减少计算值的绝对值.(进位处理,2.35变成2.4)
  • DOWN 向零方向舍入的舍入模式.从不对舍弃部分前面的数字加 1 即截尾.注意,此舍入模式始终不会增加计算值的绝对值.(直接删除多余的小数位,如2.35会变成2.3)
  • CEILING 向正无穷方向舍入
  • FLOOR 向负无穷方向舍入
  • HALF_EVEN 向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP ,如果是偶数,使用ROUND_HALF_DOWN
  • UNNECESSARY 计算结果是精确的,不需要舍入模式

各个模式的值:

Input NumberUPDOWNCEILINGFLOORHALF_UPHALF_DOWNHALF_EVENUNNECESSARY
5.56565656throw ArithmeticException
2.53232322throw ArithmeticException
1.62121222throw ArithmeticException
1.12121111throw ArithmeticException
1.011111111
-1.1-2-1-1-2-1-1-1throw ArithmeticException
-1.6-2-1-1-2-2-2-2throw ArithmeticException
-2.5-3-2-2-3-3-2-2throw ArithmeticException
-5.5-6-5-5-6-6-5-6throw ArithmeticException

源码分析

构造函数

从参数为double的构造函数看起:

1
2
3
public BigDecimal(double val) {
this(val,MathContext.UNLIMITED);
}

会默认使用UNLIMITED做为MathContext(在构建BigDecimal的时候,可以传入MathContext对象,指定精确到的小数以及舍入方式),
UNLIMITED这个MathContext代表不限制精度,并且采用四舍五入.

1
2
public static final MathContext UNLIMITED =
new MathContext(0, RoundingMode.HALF_UP);

具体来看看BigDecimal是如何初始化一个double的.

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
public BigDecimal(double val, MathContext mc) {
if (Double.isInfinite(val) || Double.isNaN(val)) // 如果参数是正无穷或者是负无穷,以及是非数字的时候直接抛异常
throw new NumberFormatException("Infinite or NaN");
// Translate the double into sign, exponent and significand, according
// to the formulae in JLS, Section 20.10.22.
long valBits = Double.doubleToLongBits(val);
int sign = ((valBits >> 63) == 0 ? 1 : -1);
int exponent = (int) ((valBits >> 52) & 0x7ffL);
long significand = (exponent == 0
? (valBits & ((1L << 52) - 1)) << 1
: (valBits & ((1L << 52) - 1)) | (1L << 52));

exponent -= 1075;
// At this point, val == sign * significand * 2**exponent.


if (significand == 0) {
this.intVal = BigInteger.ZERO;
this.scale = 0;
this.intCompact = 0;
this.precision = 1;
return;
}
// Normalize
while ((significand & 1) == 0) { // i.e., significand is even
significand >>= 1;
exponent++;
}
int scale = 0;
// Calculate intVal and scale
BigInteger intVal;
long compactVal = sign * significand;
if (exponent == 0) {
intVal = (compactVal == INFLATED) ? INFLATED_BIGINT : null;
} else {
if (exponent < 0) {
intVal = BigInteger.valueOf(5).pow(-exponent).multiply(compactVal);
scale = -exponent;
} else { // (exponent > 0)
intVal = BigInteger.valueOf(2).pow(exponent).multiply(compactVal);
}
compactVal = compactValFor(intVal);
}
int prec = 0;
int mcp = mc.precision;
if (mcp > 0) { // do rounding
int mode = mc.roundingMode.oldMode;
int drop;
if (compactVal == INFLATED) {
prec = bigDigitLength(intVal);
drop = prec - mcp;
while (drop > 0) {
scale = checkScaleNonZero((long) scale - drop);
intVal = divideAndRoundByTenPow(intVal, drop, mode);
compactVal = compactValFor(intVal);
if (compactVal != INFLATED) {
break;
}
prec = bigDigitLength(intVal);
drop = prec - mcp;
}
}
if (compactVal != INFLATED) {
prec = longDigitLength(compactVal);
drop = prec - mcp;
while (drop > 0) {
scale = checkScaleNonZero((long) scale - drop);
compactVal = divideAndRound(compactVal, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
prec = longDigitLength(compactVal);
drop = prec - mcp;
}
intVal = null;
}
}
this.intVal = intVal;
this.intCompact = compactVal;
this.scale = scale;
this.precision = prec;
}

add方法:

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
public BigDecimal  add(BigDecimal augend) {
long xs =this.intCompact; //整型数字表示的BigDecimal,例a的intCompact值为122
long ys = augend.intCompact;//同上
BigInteger fst = (this.intCompact !=INFLATED) ?null :this.intVal;//初始化BigInteger的值,intVal为BigDecimal的一个BigInteger类型的属性
BigInteger snd =(augend.intCompact !=INFLATED) ?null : augend.intVal;
int rscale =this.scale;//小数位数

long sdiff = (long)rscale - augend.scale;//小数位数之差
if (sdiff != 0) {//取小数位数多的为结果的小数位数
if (sdiff < 0) {
int raise =checkScale(-sdiff);
rscale =augend.scale;
if (xs ==INFLATED ||
(xs = longMultiplyPowerTen(xs,raise)) ==INFLATED)

fst =bigMultiplyPowerTen(raise);
}else {
int raise =augend.checkScale(sdiff);
if (ys ==INFLATED ||(ys =longMultiplyPowerTen(ys,raise)) ==INFLATED)
snd = augend.bigMultiplyPowerTen(raise);
}
}
if (xs !=INFLATED && ys !=INFLATED) {
long sum = xs + ys;
if ( (((sum ^ xs) &(sum ^ ys))) >= 0L)//判断有无溢出
return BigDecimal.valueOf(sum,rscale);//返回使用BigDecimal的静态工厂方法得到的BigDecimal实例
}
if (fst ==null)
fst =BigInteger.valueOf(xs);//BigInteger的静态工厂方法
if (snd ==null)
snd =BigInteger.valueOf(ys);
BigInteger sum =fst.add(snd);
return (fst.signum == snd.signum) ?new BigDecimal(sum,INFLATED, rscale, 0) :
new BigDecimal(sum,compactValFor(sum),rscale, 0);//返回通过其他构造方法得到的BigDecimal对象
}