前言
今天群里一个初级开发者问为什么测试人员测出来他写的价格计算模块有计算偏差的问题,他检查了半天也没找出问题。这里小胖哥要提醒你,商业计算请务必使用
BigDecimal
,浮点做商业运算是不精确的。因为计算机无法使用二进制小数来精确描述我们程序中的十进制小数。《Effective Java》在第48条也推荐“使用BigDecimal来做精确运算”。今天我们就来总结归纳其相关的知识点。
1. BigDecimal
BigDecimal表示不可变的任意精度带符号十进制数。它由两部分组成:
-
intVal - 未校正精度的整数,类型为
BigInteger
-
Scale - 一个32位整数,表示小数点右边的位数
例如,BigDecimal 3.14的未校正值为314,缩放为2。我们使用BigDecimal进行高精度算术运算。我们还将它用于需要控制比例和舍入行为的计算。如果你的计算是商业计算请务必使用计算精确的BigDecimal
。
2. 构造BigDecimal
我们可以从
String
,
character
数组,
int
,
long
和
BigInteger
创建一个
BigDecimal
对象:
@Test
public void theValueMatches() {
BigDecimal bdFromString = new BigDecimal("0.12");
BigDecimal bdFromCharArray = new BigDecimal(new char[]{'3', '.', '1', '4', '1', '5'});
BigDecimal bdlFromInt = new BigDecimal(42);
BigDecimal bdFromLong = new BigDecimal(123412345678901L);
BigInteger bigInteger = BigInteger.probablePrime(100, new Random());
BigDecimal bdFromBigInteger = new BigDecimal(bigInteger);
assertEquals("0.12", bdFromString.toString());
assertEquals("3.1415", bdFromCharArray.toString());
assertEquals("42", bdlFromInt.toString());
assertEquals("123412345678901", bdFromLong.toString());
assertEquals(bigInteger.toString(), bdFromBigInteger.toString());
}
复制代码
我们还可以从
double
创建
BigDecimal
:
@Test
public void whenBigDecimalCreatedFromDouble_thenValueMayNotMatch() {
BigDecimal bdFromDouble = new BigDecimal(0.1d);
assertNotEquals("0.1", bdFromDouble.toString());
}复制代码
我们发现在这种情况下,结果与预期的结果不同(即0.1)。这是因为:这个转换结果是
double
的二进制浮点值的精确十进制表示,其值得结果不是我们可以预测的.我们应该使用
String
构造函数而不是
double
构造函数。另外,我们可以使用
valueOf
静态方法将
double
转换为
BigDecimal
或者直接使用其未校正数加小数位数 :
@Test
public void whenBigDecimalCreatedUsingValueOf_thenValueMatches() {
BigDecimal bdFromDouble = BigDecimal.valueOf(0.1d);
BigDecimal bigFromLong=BigDecimal.valueOf(1,1);
assertEquals("0.1", bdFromDouble.toString());
assertEquals("0.1", bigFromLong.toString());
}复制代码
在转换为BigDecimal之前,此方法将double转换为其String表示形式。此外,它可以重用对象实例。因此,我们应该优先使用valueOf方法来构造函数。
3. 常用API
方法名
|
对应方法相关用法解释 |
---|---|
abs()
|
绝对值,scale不变 |
add(BigDecimal augend)
|
加,scale为augend和原值scale的较大值 |
subtract(BigDecimal augend) | 减,scale为augend和原值scale的较大值 |
multiply(BigDecimal multiplicand) | 乘,scale为augend和原值scale的和 |
divide(BigDecimal divisor) | 除,原值/divisor,如果不能除尽会抛出异常,scale与原值一致 |
divide(BigDecimal divisor, int roundingMode) | 除,指定舍入方式,scale与原值一致 |
divide(BigDecimal divisor, int scale, int roundingMode) | 除,指定舍入方式和scale |
remainder(BigDecimal divisor)
|
取余,scale与原值一致 |
divideAndRemainder(BigDecimal divisor)
|
除法运算后返回一个数组存放除尽和余数 如
23/3
返回
{7,2}
|
divideToIntegralValue(BigDecimal divisor) | 除,只保留整数部分,但scale仍与原值一致 |
max(BigDecimal val)
|
较大值,返回原值与val中的较大值,与结果的scale一致 |
min(BigDecimal val)
|
较小值,与结果的scale一致 |
movePointLeft(int n)
|
小数点左移,scale为原值scale+n |
movePointRight(int n)
|
小数点右移,scale为原值scale+n |
negate()
|
取反,scale不变 |
pow(int n) | 幂,原值^n,原值的n次幂 |
scaleByPowerOfTen(int n) | 相当于小数点右移n位,原值*10^n |