首页 > 其他分享 >从BigDecimal的divide的异常说起

从BigDecimal的divide的异常说起

时间:2024-02-06 18:23:35浏览次数:28  
标签:scale BigDecimal int 说起 相除 new divide

在过去做项目的某一天中,突然有小伙伴说两个BigDecimal的数据相除(divide)报错了,觉得不可能,然后问他是怎么编写的,他说很简单呀,就是new了2个BigDecimal,然后相除的结果赋值给另外一个BigDecimal对象。听起来觉得没有问题,正常来说,2个Integer(int),2个Double(double)都不会报错,然后问是什么异常,说是一个很奇怪的异常。

问不出情况,直接看代码吧(省去了一些其他无关的代码)

//省略业务的逻辑 ...

//得到订单金额
BigDecimal orderAmount = new BigDecimal(2312.23);
//得到成本
BigDecimal cost = new BigDecimal(984.23);
//得到客户数
BigDecimal customerCount = new BigDecimal(35);
//得到客单价
BigDecimal pricePerCustomer = orderAmount.divide(customerCount);
log.info("客单价:{}", pricePerCustomer);

//得到毛利率
BigDecimal profit = (orderAmount.subtract(cost)).divide(cost);
log.info("毛利率:{}", profit);

//省去其他逻辑...

代码看起来没什么问题(一般的小伙伴),为什么会报错呢?来看下异常信息,如下:

11:01:10.027 [main] INFO net.jhelp.demo.BigDecimalTest - 客单价:66.06371428571428623399697244167327880859375

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

at java.math.BigDecimal.divide(BigDecimal.java:1690)

异常的信息是:

算术运算异常:相除结果为无限小数;没有可精确表示的计算结果。

从异常中可以看出是“计算结果除不尽,没办法确定相除的结果”,从这个信息的提示上看,是要给BigDecimal的“商”设置计算结果的“小数位”。

但是很奇怪,上面的程序中有2处用了divide方法,一处没有报错,一处报错,也就是用divide方法不一定会抛出上面的异常,带上面的问题,来调试一下程序,找到BigDecimal的divide方法在里设置一个断点,当执行到第一个divide时的调试如下:

640_11.png

从上面可以看到,在代码里面直接new BigDecimal(2312.23),实际上在程序运行时,它变成了一个带41位小数的浮点数(scale=41)(题外话:在构造确实数值时,还是使用new BigDecimal("2312.23"),这样出来就不会带过的小数位),另外一个数值为new BigDecimal(35),就是35的值,精度为0(scale=0)。图上还看到一个属性(intCompact),有不同的值(-9223372036854775808, 35).

 

new BigDecimal(2312.23)实际上调用的是BigDecimal中的浮点数构造方法:

public BigDecimal(Double value){
   .....   
   this.intVal = intVal;
   this.intCompact = compactVal;
   this.scale = scale;
   this.precision = prec;
}

而new BigDecimal(35)调用的是BigDecimal中的整数构造方法:

public BigDecimal(int val) {
        this.intCompact = val;
        this.scale = 0;
        this.intVal = null;
    }

再继续往下走,来看看preferredScale(预期的小数位精度)是多少。

640_1.png

这时的preferredScale=41, 继续往下看。

640_2.png

640_3.png

640_4.png

从上图中看到,BigDecimal在计算2个除数的商时,如果被除数是整数,那商的精度由除数的精度决定(从计算preferredScale可以看出)。另外,还生成了一个MathContext的对象,默认进位规则是:RoundingMode.UNNECESSARY。

这里面有一个重要的分支

640_5.png

当BigDecimal是一个浮点类型的数时,它的intCompact是和INFLATED的值相同的。

方法的调用流程是这样:

640_7.png

最终返回一个跟“除数”一样精度的“商”值。

来看第二个divide的跟踪过程

640 (1).png

640 (2).png

第二个divide的除数,被除数的scale=41, intCompact的值相同(-9223372036854775808==INFLATED),preferredScale=0,而这时的MathContext 与前一个有很大的不同,它的precision=192.

640 (3).png

this.precision() = 45
(long)Math.ceil(10.0*divisor.precision()/3.0 = 147

而在这个逻辑分支时,2个浮点相除的,走的是下面的方法,和第一个计算时走的分支是不同的,第一个计算走的是上面的方法。

640 (4).png

来看看它的方法调用流程:

640 (5).png

640 (6).png

    /**
     * Shared logic of need increment computation
.     */
    private static boolean commonNeedIncrement(int roundingMode, int qsign,                                     int cmpFracHalf, boolean oddQuot) {

        switch(roundingMode) {
        case ROUND_UNNECESSARY:
            throw new ArithmeticException("Rounding necessary");
case ROUND_UP: // Away from zero return true;
case ROUND_DOWN: // Towards zero return false;          //略...   }

跟踪到这里,是不是清晰很多了,这里的roundingMode就是在调用divide的方法中获取的,也是是前面的MathContext对象生成时,默认设置的RoundingMode.UNNECESSARY,从这个方法的调用过程中,可以看出divide(BigDecimal divisor)方法不适合用于2个数都是“浮点数“的场景的相除。(题外话,这里抛出的异常,与外面见到的异常是不一样的,有点坑,为什么这个方法不标志一下@Deprecated,让别人不要掉坑里。)

总结

上面根据异常的信息,对BigDecimal的divide(除法)的源代码分析与跟踪,分析出现问题的原因,在开发的过程中,不要使用divide(BigDecimal divisor)这方法,以免掉到坑里,尽理使用divide(BigDecimal divisor, int scale, int roundingMode)这种,指定精度,和“舍入规则”。

<end>

标签:scale,BigDecimal,int,说起,相除,new,divide
From: https://www.cnblogs.com/zhanghengscnc/p/18010150

相关文章

  • JAVA之BigDecimal详解
    一、BigDecimal概述Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float......
  • [LeetCode] 2966. Divide Array Into Arrays With Max Difference
    Youaregivenanintegerarraynumsofsizenandapositiveintegerk.Dividethearrayintooneormorearraysofsize3satisfyingthefollowingconditions:Eachelementofnumsshouldbeinexactlyonearray.Thedifferencebetweenanytwoelementsin......
  • 全流程机器视觉工程开发(三)任务前瞻 - 从opencv的安装编译说起,到图像增强和分割
    前言最近开始做这个裂缝识别的任务了,大大小小的问题我已经摸得差不多了,然后关于识别任务和分割任务我现在也弄的差不多了。现在开始做正式的业务,也就是我们说的裂缝识别的任务。作为前言,先来说说场景:现在相机返回过来的照片:都是jpeg格式的照片,当然也可能是别的格式,目前主流是......
  • android navigationBarDividerColor 无效
    AndroidnavigationBarDividerColor无效问题解析与解决1.问题背景在开发Android应用程序时,我们经常会使用导航栏(NavigationBar)来提供用户导航和操作的功能。导航栏中的分割线(divider)是一种常见的设计元素,用于分隔不同的导航按钮或操作按钮。在Android中,我们可以使用navigationB......
  • Android navigationBarDividerColor
    实现AndroidnavigationBarDividerColor的步骤流程图flowchartTDA(开始)B(查找navigationBar对象)C(创建dividerDrawable对象)D(设置dividerDrawable为navigationBar的dividerDrawable属性)E(结束)A-->B-->C-->D-->E介绍在Android开发......
  • 陈文自媒体:影视解说起号逻辑!
    1、还是要定位,赛道定位,内容定位,人群属性定位2、建议细分领域定位,短视频哪个赛道都是火海,所以得要细分,比如历史领域,古代历史、现代历史,人文历史,地理历史,以此类推自己在细分,选择一个自己喜欢的即可。3、在短视频世界,细分赛道依然能养活你全家了,不用担心吃不饱。4、还是要想办法先涨粉......
  • Bigdecimal四则运算怎么减少精度损失
    在进行BigDecimal的四则运算时,通常建议将除法运算放在最后进行,以减小精度损失。这是因为在除法运算中,小数位数可能会增加,导致结果的精度减小。具体来说,如果你按照以下顺序进行四则运算,可以最大程度地保持精度:加法和减法:可以按照需要进行加法和减法运算,这不太会影响精度。乘......
  • Java浮点数精度问题与BigDecimal详解
    第1章:引言大家好,我是小黑,咱们在日常的Java编程中,经常会遇到处理金融数据的情况,比如计算商品的价格或者处理用户的账户余额。在这些场景下,精确的数值计算就显得尤为重要。这时候,BigDecimal就成了咱们的好帮手。不像普通的float和double类型,BigDecimal提供了非常精确的数值计算。......
  • BigDecimalUtil 工具类
    packagecom.yintn.cbms.basicinfo.api.utils;importlombok.experimental.UtilityClass;importjava.math.BigDecimal;importjava.math.BigInteger;importjava.math.RoundingMode;importjava.util.Optional;@UtilityClasspublicclassBigDecimalUtil{pu......
  • 数据资产入表,从数据产权三权分置说起
    数据资产在会计上入表的前提,必须是企业的数据才能入表。数据的确权需要通过数据产权结构性分置来确定,明确具体什么人对什么数据拥有什么权利。其实数据产权的结构分制不是什么新话题,实际上是有土地产权分制,当时提出土地的产权分制就是想激活土地的这个价值,同样数据产权的结构分制也......