专栏名称: 码农小胖哥
技术公众号:码农小胖哥
目录
相关文章推荐
51好读  ›  专栏  ›  码农小胖哥

Java开发中商业计算请务必使用BigDecimal来进行计算!

码农小胖哥  · 掘金  ·  · 2019-09-21 14:24

正文

阅读 36

Java开发中商业计算请务必使用BigDecimal来进行计算!

前言

今天群里一个初级开发者问为什么测试人员测出来他写的价格计算模块有计算偏差的问题,他检查了半天也没找出问题。这里小胖哥要提醒你,商业计算请务必使用 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







请到「今天看啥」查看全文