实战小技巧18:BigDecimal除法使用不当导致精度问题

简介: 在使用BigDecimal的除法时,遇到一个鬼畜的问题,本以为的精度计算,结果使用返回0,当然最终发现还是使用姿势不对导致的,因此记录一下,避免后面重蹈覆辙

在使用BigDecimal的除法时,遇到一个鬼畜的问题,本以为的精度计算,结果使用返回0,当然最终发现还是使用姿势不对导致的,因此记录一下,避免后面重蹈覆辙


I. 问题抛出



在使用BigDecimal做高精度的除法时,一不注意遇到了一个小问题,如下

@Test
public void testBigDecimal() {
    BigDecimal origin = new BigDecimal(541253);
    BigDecimal now = new BigDecimal(12389431);
    BigDecimal val = origin.divide(now, RoundingMode.HALF_UP);
    System.out.println(val);
    origin = new BigDecimal(541253);
    now = new BigDecimal(12389431.3);
    val = origin.divide(now, RoundingMode.HALF_UP);
    System.out.println(val);
    origin = new BigDecimal(541253.4);
    now = new BigDecimal(12389431);
    val = origin.divide(now, RoundingMode.HALF_UP);
    System.out.println(val);
}
复制代码


上面的输出是什么 ?


0
0
0.043686703610520937021487456961257
复制代码


为什么前面两个会是0呢,如果直接是 541253 / 12389431 = 0 倒是可以理解, 但是BigDecimal不是高精度的计算么,讲道理不应该不会出现这种整除的问题吧


我们知道在BigDecimal做触发时,可以指定保留小数的参数,如果加上这个,是否会不一样呢?


BigDecimal origin = new BigDecimal(541253);
BigDecimal now = new BigDecimal(12389431);
BigDecimal val = origin.divide(now, 5, RoundingMode.HALF_UP);
System.out.println(val);
复制代码


输出结果为:


0.04369
复制代码


所以说在指定了保留小数之后,则没有问题,所以大胆的猜测一下,是不是上面的几种case中,由于scale值没有指定时,默认值不一样,从而导致最终结果的精度不同呢?

简单的深入源码分析一下,执行的方式为 origin.divide(now,

RoundingMode.HALF_UP);, 所以这个scale参数就瞄准origin对象,而这个对象,就只能去分析它的构造了,因为没有其他的地方使用


II. 源码定位



1. 整形传参构造


分析下面这一行, 直接进入源码


BigDecimal origin = new BigDecimal(541253);
复制代码


很明显的int传参构造,进去简单看一下


// java.math.BigDecimal#BigDecimal(int)
public BigDecimal(int val) {
    this.intCompact = val;
    this.scale = 0;
    this.intVal = null;
}
public BigDecimal(long val) {
    this.intCompact = val;
    this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;
    this.scale = 0;
}
复制代码


so,很明确的知道默认的scale为0,也就是说当origin为正数时,以它进行的除法,不现实指定scale参数时,最终返回的都是没有小数的,同样看一眼,还有long的传参方式, BigInteger也一样


2. 浮点传参


接下来就是浮点的scale默认值确认了,这个构造相比前面的复杂一点,源码就不贴了,太长,也看不太懂做了些啥,直接用猥琐一点的方式,进入debug模式,单步执行

@Test
public void testBigDecimal() {
    BigDecimal origin = new BigDecimal(541253.0);
    BigDecimal now = new BigDecimal(12389431.1);
    BigDecimal tmp = new BigDecimal(0.0);
}
复制代码


根据debug的结果,第一个,scale为0; 第二个scale为29, 第三个scale为0

image.png

image.png

image.png


3. String传参


依然是一大串的逻辑,同样采用单步debug的方式试下


@Test
public void testBigDecimal() {
    BigDecimal origin = new BigDecimal("541253.0");
    BigDecimal now = new BigDecimal("12389431.1");
    BigDecimal t = new BigDecimal("0.0");
}
复制代码


上面三个的scale都是1


image.png


4. 小结


  • 对于BigDecimal进行除法运算时,最好指定其scale参数,不然可能会有坑
  • 对于BigDecimla的scale初始化的原理,有待深入看下BigDecimal是怎么实现的


最后贴一张乘法的图作为收尾


image.png



相关文章
|
6月前
|
Java
BigDecimal类型的数据如何做绝对值和相除求百分比
BigDecimal类型的数据如何做绝对值和相除求百分比
164 3
|
6月前
|
测试技术
【日常记录】——对BigDecimal除法运算时遇到的Bug
【日常记录】——对BigDecimal除法运算时遇到的Bug
|
6月前
|
C语言
【汇编语言实战】两个32位数的相加运算
【汇编语言实战】两个32位数的相加运算
61 2
|
5月前
|
Java
bigdecimal加减乘除
bigdecimal加减乘除
|
Java
BigDecimal详解和精度问题
BigDecimal详解和精度问题
132 0
运用BigInteger进行整数之间的高精度的加减乘除运算
运用BigInteger进行整数之间的高精度的加减乘除运算
96 0
BigDecimal加减乘除计算以及比较大小
BigDecimal加减乘除计算以及比较大小
205 0
BigDecimal加减乘除计算以及比较大小
|
Java
Java实现复数Complex的加减乘除运算、取模、求幅角角度
Java实现复数Complex的加减乘除运算、取模、求幅角角度
263 0
Java实现复数Complex的加减乘除运算、取模、求幅角角度
BigDecimal加减乘除计算
BigDecimal加减乘除计算
90 0
BigDecimal加减乘除以及大小比较
BigDecimal加减乘除以及大小比较
99 0