精确运算二 BigDecimal详解

简介

       BigInteger 和 BigDecimal 都能实现大数字的运算,不同的是 BigDecimal 加入了小数的概念。Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数。在实际应用中,需要对更大或者更小的数进行运算和处理。float和double只能用来做科学计算或者是工程计算,它们没有提供完全精确的结果,不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,在商业计算中要用BigDecimal。BigDecimal所创建的是对象,我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。

标度

       BigDecimal由非标度值和 32 位的整数标度 (scale) 组成,表示的数为: unscaledValue × 10的-scale 次幂。

       如果scale为零或正数,最终的结果中,小数点后面的位数就等于scale标度。比如: scale为1,10的-1次方, 0.1 小数点后有1位;如果scale是负数,那最终的结果将会是乘以 10的|scale| 次方比如: scale为-3 最终的值就是非标度值乘以1000 (10的(- -3)次方)。

ulp

       unit in the last place

       两个数之间的距离,在数学中是无限的,比如1和2之间有无数个数,但是在计算机中是有限的,因为计算机需要用有限个字节来表示double或者float,计算机表示不了无限的数,因为没有无限内存。

       假设两个数之间有10个数,那么ulp 就是1/10 ,1和2之间有一个数,距离为1;1.1和2.1之间有十个数,距离为0.1,这就是ulp。

       非零 BigDecimal 值的 ulp 是此值与下一个具有相同位数的较大 BigDecimal 值之间的正距离,零值的 ulp 在数值上等于1 和 this.scale()之间的距离,所以可以说所有的数的ulp为[1, this.scale()]。

实例如下:

1
2
3
4
5
6
7
8
9
System.out.println(BigDecimal.ZERO.ulp());
System.out.println(Bigdecimal.ZERO.scale());
System.out.println(Bigdecimal.ONE.ulp());
System.out.println(new Bigdecimal("232312.323422232").ulp());

1
0
1
1E-9

初始化数据

       new BigDecimal() 传参支持 integer,long,double,float,BigInteger。

1
2
3
BigDecimal.ZERO 初始化一个为0的BigDecimal对象
BigDecimal.ONE 初始化一个为1的BigDecimal对象
BigDecimal.TEN 初始化一个为10的BigDecimal对象

构造方法

1
2
3
4
5
6
7
8
BigDecimal(int) 
实例化时将整数型转换为 BigDecimal 类型。
BigDecimal(double)
实例化时将双精度型转换为 BigDecimal 类型。
BigDecimal(long)
实例化时将长整数形式转换为 BigDecimal 类型。 
BigDecimal(String)
实例化时将字符串形式转换为 BigDecimal 类型。

       如果是想将一个有小数的数字转换为BigDecimal的话,如“3.1”,那么最好用BigDecimal(String),即new BigDecimal(“3.1”),而不能用new BigDecimal(3.1),因为这样会导致精度缺失,实际得出的值不等于3.1,jdk中也明确说明了不推荐使用new BigDecimal(Double),并且new BigDecimal(String)的效率比new BigDecimal(Double)要高,还有一个方法就是BigDecimal(BigInteger,int),可以使用new BigDecimal(BigInteger.valueOf(31),1)来得到3.1,它的效率也很高。在jdk6.0中加入了BigDecimal(int)的构造函数,所以当被转换的数值是整数时,也可以用它。

其余构造方法

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
BigDecimal(BigInteger val) 
将 BigInteger 转换为 BigDecimal。

BigDecimal(BigInteger unscaledVal, int scale)
将 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, MathContext mc)
double 转换为 BigDecimal(根据上下文设置进行舍入)。

BigDecimal(int val, MathContext mc)
int 转换为 BigDecimal(根据上下文设置进行舍入)。

BigDecimal(long val, MathContext mc)
long 转换为 BigDecimal(根据上下文设置进行舍入)。

BigDecimal(String val, MathContext mc)
将 BigDecimal 的字符串表示形式转换为 BigDecimal,接受与 BigDecimal(String) 构造方法相同的字符串(按照上下文设置进行舍入)。

方法

加减乘除

方法名称 说明
BigDecimal add(BigDecimal augend) 做加法运算,返回一个 BigDecimal,其值为 (this + augend),其标度为 max(this.scale(), augend.scale())。
BigDecimal subtract(BigDecimal subtrahend) 做减法运算,返回一个 BigDecimal,其值为 (this - subtrahend),其标度为 max(this.scale(), subtrahend.scale())。
BigDecimal multiply(BigDecimal multiplieand) 做乘法运算,返回一个 BigDecimal,其值为 (this × multiplicand),其标度为 (this.scale() + multiplicand.scale())。
BigDecimal divide(BigDecimal divisor,int scale,int roundingMode ) 做除法运算,返回一个 BigDecimal,其值为 (this / divisor),其标度为指定标度。

       其中,divide() 方法的 3 个参数分别表示除数、商的小数点后的位数和近似值处理模式(详见BigDecimal的舍入方式

其它方法:

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
BigDecimal abs() 
返回 BigDecimal,其值为此 BigDecimal 的绝对值,其标度为 this.scale()

BigDecimal abs(MathContext mc)
返回其值为此 BigDecimal 绝对值的 BigDecimal(根据上下文设置进行舍入)。

BigDecimal add(BigDecimal augend, MathContext mc)
返回其值为 (this + augend) 的 BigDecimal(根据上下文设置进行舍入)。

byte byteValueExact()
将此 BigDecimal 转换为 byte,以检查丢失的信息。

int compareTo(BigDecimal val)
将此 BigDecimal 与指定的 BigDecimal 比较。

BigDecimal divide(BigDecimal divisor)
返回一个 BigDecimal,其值为 (this / divisor),其首选标度为 (this.scale() - divisor.scale());如果无法表示准确的商值(因为它有无穷的十进制扩展),则抛出 ArithmeticException。

BigDecimal divide(BigDecimal divisor, int roundingMode)
返回一个 BigDecimal,其值为 (this / divisor),其标度为 this.scale()

BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
返回一个 BigDecimal,其值为 (this / divisor),其标度为指定标度。

BigDecimal divide(BigDecimal divisor, MathContext mc)
返回其值为 (this / divisor) 的 BigDecimal(根据上下文设置进行舍入)。

BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
返回一个 BigDecimal,其值为 (this / divisor),其标度为 this.scale()

BigDecimal[] divideAndRemainder(BigDecimal divisor)
返回由两个元素组成的 BigDecimal 数组,该数组包含 divideToIntegralValue 的结果,后跟对两个操作数计算所得到的 remainder。

BigDecimal[] divideAndRemainder(BigDecimal divisor, MathContext mc)
返回由两个元素组成的 BigDecimal 数组,该数组包含 divideToIntegralValue 的结果,后跟根据上下文设置对两个操作数进行舍入计算所得到的 remainder 的结果。

BigDecimal divideToIntegralValue(BigDecimal divisor)
返回 BigDecimal,其值为向下舍入所得商值 (this / divisor) 的整数部分。

BigDecimal divideToIntegralValue(BigDecimal divisor, MathContext mc)
返回 BigDecimal,其值为 (this / divisor) 的整数部分。

double doubleValue()
将此 BigDecimal 转换为 double

boolean equals(Object x)
比较此 BigDecimal 与指定的 Object 的相等性。

float floatValue()
将此 BigDecimal 转换为 float

int hashCode()
返回此 BigDecimal 的哈希码。

int intValue()
将此 BigDecimal 转换为 int

int intValueExact()
将此 BigDecimal 转换为 int,以检查丢失的信息。

long longValue()
将此 BigDecimal 转换为 long

long longValueExact()
将此 BigDecimal 转换为 long,以检查丢失的信息。

BigDecimal max(BigDecimal val)
返回此 BigDecimal 和 val 的最大值。

BigDecimal min(BigDecimal val)
返回此 BigDecimal 和 val 的最小值。

BigDecimal movePointLeft(int n)
返回一个 BigDecimal,它等效于将该值的小数点向左移动 n 位。如果 n 为负数,则该调用等效于 movePointRight(-n),如果 n 为非负数,则调用仅将 n 添加到该标度。返回的值和标度分别为: (this*10^-n),max(this.scale()+n,0)。

BigDecimal movePointRight(int n)
返回一个 BigDecimal,它等效于将该值的小数点向右移动 n 位。如果 n 为负,则该调用等效于 movePointLeft(-n),如果 n 为非负数,则该调用仅从该标度减去 n,返回的值和标度分别为: (this*10^n),max(this.scale()-n,0)。

BigDecimal multiply(BigDecimal multiplicand, MathContext mc)
返回其值为 (this × multiplicand) 的 BigDecimal(根据上下文设置进行舍入)。

BigDecimal negate()
返回 BigDecimal,其值为 (-this),其标度为 this.scale()

BigDecimal negate(MathContext mc)
返回其值为 (-this) 的 BigDecimal(根据上下文设置进行舍入)。

BigDecimal plus()
返回 BigDecimal,其值为 (+this),其标度为 this.scale()

BigDecimal plus(MathContext mc)
返回其值为 (+this) 的 BigDecimal(根据上下文设置进行舍入)。

BigDecimal pow(int n)
返回其值为 (this ^n) 的 BigDecimal,准确计算该幂,使其具有无限精度。

BigDecimal pow(int n, MathContext mc)
返回其值为 (this ^n) 的 BigDecimal。

int precision()
返回此 BigDecimal 的精度。(精度是非标度值的数字个数。)零值的精度是 1

BigDecimal remainder(BigDecimal divisor)
返回其值为 (this % divisor) 的 BigDecimal。

BigDecimal remainder(BigDecimal divisor, MathContext mc)
返回其值为 (this % divisor) 的 BigDecimal(根据上下文设置进行舍入)。

BigDecimal round(MathContext mc)
返回根据 MathContext 设置进行舍入后的 BigDecimal。

int scale()
返回此 BigDecimal 的标度。

BigDecimal scaleByPowerOfTen(int n)
返回其数值等于 (this * 10^n) 的 BigDecimal,该结果的标度为:(this.scale()-n)。

BigDecimal setScale(int newScale)
返回一个 BigDecimal,其标度为指定值,其值在数值上等于此 BigDecimal 的值。

BigDecimal setScale(int newScale, int roundingMode)
返回一个 BigDecimal,其标度为指定值,其非标度值通过此 BigDecimal 的非标度值乘以或除以十的适当次幂来确定,以维护其总值。

BigDecimal setScale(int newScale, RoundingMode roundingMode)
返回 BigDecimal,其标度为指定值,其非标度值通过此 BigDecimal 的非标度值乘以或除以十的适当次幂来确定,以维护其总值。

short shortValueExact()
将此 BigDecimal 转换为 short,以检查丢失的信息。

int signum()
返回此 BigDecimal 的正负号函数,负、零或正时,返回 -1、0 或 1。

BigDecimal stripTrailingZeros()
返回数值上等于此小数,但从该表示形式移除所有尾部零的 BigDecimal。形式转换,数值是相等的,转换为去掉所有尾部的0的形式的数值,例如800.000去掉所有的0就是8,准换后为8乘以10的平方。

BigDecimal subtract(BigDecimal subtrahend, MathContext mc)
返回其值为 (this - subtrahend) 的 BigDecimal(根据上下文设置进行舍入)。

BigInteger toBigInteger()
将此 BigDecimal 转换为 BigInteger。

BigInteger toBigIntegerExact()
将此 BigDecimal 转换为 BigInteger,以检查丢失的信息。

String toEngineeringString()
返回此 BigDecimal 的字符串表示形式,需要指数时,则使用工程计数法。

String toPlainString()
返回不带指数字段的此 BigDecimal 的字符串表示形式。

String toString()
返回此 BigDecimal 的字符串表示形式,如果需要指数,则使用科学记数法。

BigDecimal ulp()
返回此 BigDecimal 的 ulp(最后一位的单位)的大小。

BigInteger unscaledValue()
返回其值为此 BigDecimal 的非标度值 的 BigInteger。

BigDecimal valueOf(double val)
使用 Double.toString(double) 方法提供的 double 规范的字符串表示形式将 double 转换为 BigDecimal。

BigDecimal valueOf(long val)
long 值转换为具有零标度的 BigDecimal。

BigDecimal valueOf(long unscaledVal, int scale)
long 非标度值和 int 标度转换为 BigDecimal。

部分实例

加减乘除

1
2
3
4
5
6
BigDecimal b1 = new BigDecimal("1");
BigDecimal b2 = new BigDecimal("0.11");
System.out.println(b1.add(b2));
System.out.println(b1.subtract(b2));
System.out.println(b1.multiply(b2));
System.out.println(b1.divide(b2,2,BigDecimal.ROUND_HALF_UP));//四舍五入保留两位小数

       使用divide()方法注意:相除的时候,被除数为0,会抛出异常:java.lang.ArithmeticException: Division by zero。
       相除后为小数,则应该保留小数位,否则可能会抛出异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result。

乘方

1
2
3
4
5
BigDecimal b1 = new BigDecimal("1.11");
//二次方
System.out.println(b1.pow(2));
//三次方
System.out.println(b1.pow(3));

比较大小

1
2
3
BigDecimal b1 = new BigDecimal("1.11"); 
BigDecimal b2 = new BigDecimal("0.11");
//大于0 则b1>b2;等于0 则b1==b2; 小于0 则b1<b2 System.out.println(b1.compareTo(b2));

精度

1
2
3
4
5
BigDecimal b1 = new BigDecimal("11.111");
//位数
System.out.println(b1.precision());//5
//小数点后有多少位
System.out.println(b1.scale());//3

格式化例子

       由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。以利用BigDecimal对货币和百分比格式化为例,首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

1
2
3
4
5
6
7
8
9
10
11
NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用 
NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用
percent.setMaximumFractionDigits(3); //百分比小数点最多3位

BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率
BigDecimal interest = loanAmount.multiply(interestRate); //相乘

System.out.println("贷款金额:\t" + currency.format(loanAmount));
System.out.println("利率:\t" + percent.format(interestRate));
System.out.println("利息:\t" + currency.format(interest));

       运行结果如下:

1
2
3
贷款金额:    ¥15,000.48
利率: 0.8%
利息: ¥120.00

代码详见GitHub:AccuracyCalculationTest

参考资料:
Ruthless BigDecimal用法详解
java入门教程 java BigInteger类和BigDecimal类
OpenFire_ 高精度操作数值 BigDecimal类和BinInteger类
noteless [十七]基础类型BigDecimal简介
来醉一场 BigDecimal 详解

Fork me on GitHub