• 180706-BigDecimal除法的精度问题


    logo

    BigDecimal除法的精度问题

    在使用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 = <span class="hljs-keyword">new</span> BigDecimal(<span class="hljs-number">541253</span>);
    now = <span class="hljs-keyword">new</span> BigDecimal(<span class="hljs-number">12389431.3</span>);
    val = origin.divide(now, RoundingMode.HALF_UP);
    System.out.println(val);
    
    origin = <span class="hljs-keyword">new</span> BigDecimal(<span class="hljs-number">541253.4</span>);
    now = <span class="hljs-keyword">new</span> BigDecimal(<span class="hljs-number">12389431</span>);
    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

    origin

    now

    tmp

    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

    smaple

    4. 小结

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

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

    mul

    II. 其他

    1. 一灰灰Blog: https://liuyueyi.github.io/hexblog

    一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    2. 声明

    尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    原文地址:https://www.cnblogs.com/yihuihui/p/9275298.html

  • 相关阅读:
    easyui datagrid client搜索、分页、排序
    tomcat安全配置之禁用Directory Listing
    关于一哥们离职
    <微软的软件测试之道>读书笔记3
    hdu 1685 Booksort (IDA*)
    百度——LBS.云 v2.0——创建自己的地理云数据
    Sublime Text 常用快捷键
    第二节,CCSpriteBatchNode CCSpriteFrameCache
    [置顶] hdu 1890 伸展树区间翻转
    @余凯_西二旗民工 【SVM之菜鸟实现】—5步SVM
  • 原文地址:https://www.cnblogs.com/jpfss/p/11226504.html
Copyright © 2020-2023  润新知