神奇在 Double 转换异常

异常

线上收到告警, 有以下异常

java.lang.ArithmeticException: input is infinite or NaN
        at com.google.common.math.DoubleMath.roundIntermediate(DoubleMath.java:59)
        at com.google.common.math.DoubleMath.roundToLong(DoubleMath.java:156)

        .......................

代码

对应的业务代码如下:

public static Long formatLongDefaultNegativeOne(Object obj) {
        String objStr = String.valueOf(obj);
        return !StringUtils.isBlank(objStr) &&
               !objStr.equals("NaN") && 
               !objStr.equals("Infinity") && 
               !objStr.equals("null") ? 
                    DoubleMath.roundToLong(Double.valueOf(String.valueOf(obj)),  RoundingMode.HALF_EVEN) : 
                    -1L;
    }

抛出异常的地方做了如下判断:

......
    if (!isFinite(x)) {
      throw new ArithmeticException("input is infinite or NaN");
    }
......
......
//判断是否在有限范围内
static boolean isFinite(double d) {
    return getExponent(d) <= MAX_EXPONENT;
}

getExponent(d) 是获取当前 double值的指数 . MAX_EXPONENTDouble最大值的指数 .

getExponent(d) 的方法注释中有以下说明:

If the argument is NaN or infinite, then the result is Double.MAX_EXPONENT + 1.

If the argument is zero or subnormal, then the result is Double.MIN_EXPONENT -1.

判断不在有效范围内的, 只有两种情况 , double 值是 NaNInfinity .

分析

主要逻辑都在这行代码中:

DoubleMath.roundToLong(Double.valueOf(String.valueOf(obj)))

从上面的异常中可以得知 , DoubleMath.roundToLong方法得到是 NaNInfinity . 即 Double.valueOf() == NaN || Double.valueOf() == Infinity .

业务方法的这个代码 formatLongDefaultNegativeOne(Object obj) 是从上游获取的数值 , 业务上已经限定了只能是数值类型. 对于从上游得到的数值 , 理论上是只有这几种情况了.

  • null
  • NaN
  • Infinity
  • 正常数值

对于前三种 , 逻辑上已经做了判断 , 但结果还是抛出了异常 . 说明数值不在上面四种情况里 .

当前也没有想法具体是什么样的数值才能导致这个异常 . 由于缺少线上数据的支撑 , 无法继续下去 . 只能后面再解决定位了.

未完待续.......

后记

线上加了日志, 才终于发现了问题。

这个数据是 -Infinity 。 是负无限 !

本应是正数计算的逻辑里, 竟然出现了负数。 所以一直也没往负数的方向上去想。

找到问题就好解决了 。

  • 解决业务里出现负数的业务逻辑
  • 不兼容负数场景 , 但要有个 try - catch 。

这个神奇的异常算是结束了。 同时也做了个警醒 , 对待无限 , 有正无限, 还有负无限!


如果文章有帮助到您,请点个赞,您的反馈会让我感到文章是有价值的

posted @ 2020-06-19 15:46  孙行者、  阅读(425)  评论(0编辑  收藏  举报