Java.lang.NumberFormatException: Infinite or NaN,怎么破?

开发 前端
在Java中,​浮点数0并非一个准确值,而是一个无限接近0的数。为此才闹出这么多令人费解的“幺蛾子”,这是由计算机底层原理决定的,记住就好,无解。

你好,我是YourBatman:当我老了,也写代码;不为别的,只为爱好。

📚前言

如果你工作超5年,100%遇到过这个异常:java.lang.NumberFormatException: Infinite or NaN

图片

  • Infinite中文释义:极大的、无法衡量的、无穷尽的;
  • NaN:Not a Number,不是一个数,它是计算机科学中数据类型的一种,代表不可表示的值,常用于浮点数计算中,于1985年纳入浮点数标准IEEE 754。

在 Java 中只有浮点类型(Float&Double)实现了IEEE 754标准

它还有些变种异常:阅完本文就知道这些异常本质上其实是一回事了

  • java.lang.NumberFormatException: For input string: NaN
  • java.sql.SQLException: 'NaN' is not a valid numeric or approximate numeric value

✍正文

java.lang.NumberFormatException: Infinite or NaN异常并不算常见(毕竟开发中浮点数远远没有整数使用场景多),但也绝不罕见。so,知道为何会出现此异常,以及如何解决它是每个开发者必知必会的知识点。

🌈异常哪里抛出来的?

(假设你看不到异常栈)从抛出的异常中可以提取到两个关键信息供以我们查找异常源头:

  1. 异常类型:java.lang.NumberFormatException
  2. 异常detail msg:Infinite or NaN

首先当然是利用Java语言强类型的优势,看看哪些地方引用到了java.lang.NumberFormatExceptionNumberFormatException:

图片

OMG,在641个地方出现过,看到这个数字该当场死心了:这条信息基本就是无效信息。

无奈再根据关键字Infinite or NaN搜索试试:

图片

太幸运了,有且仅有一处代码里存在。看看是哪里:

图片

破案了:** java.lang.NumberFormatException: Infinite or NaN异常有且仅在构造BigDecimal实例的时候才有可能抛出。**

🌈抛出此异常的原因

既然抛出此异常的源码都找到了,并且还只有一处,回答此问题就非常容易了:

public BigDecimal(double val, MathContext mc) {
if (Double.isInfinite(val) || Double.isNaN(val))
throw new NumberFormatException("Infinite or NaN");

... // 省略其它代码
}

逻辑简单,将Double的两个方法isInfinite()和isNaN()一看便知:

public final class Double extends Number implements Comparable<Double> {

// 常量
public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
public static final double NaN = 0.0d / 0.0;

public static boolean isInfinite(double v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}
public static boolean isNaN(double v) {
return (v != v);
}

}

一个个来。

🚀isInfinite(double v)

public static final double POSITIVE_INFINITY = 1.0 / 0.0;
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;

public static boolean isInfinite(double v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}

将v和两个常量比较而已,逻辑不可谓不简单。那么关键点来了:什么情况下一个double类型的值会和POSITIVE_INFINITY/NEGATIVE_INFINITY常量相等呢?

其实看Double类对这两个常量的定义,就明白了(参考👆🏻常量定义代码)。为了更清晰的对号入座,笔者这里再来几个举一反三的case:

@Test
public void fun2() {
// 等于Double.POSITIVE_INFINITY的场景
System.out.println(1.0 / 0 == Double.POSITIVE_INFINITY); // true
System.out.println(2.0 / 0 == Double.POSITIVE_INFINITY); // true
System.out.println(1 / 0.0 == Double.POSITIVE_INFINITY); // true
System.out.println(2 / 0.0 == Double.POSITIVE_INFINITY); // true
System.out.println(new Double(Double.POSITIVE_INFINITY) == Double.POSITIVE_INFINITY); // true

// 等于Double.NEGATIVE_INFINITY的场景
System.out.println(-1.0 / 0 == Double.NEGATIVE_INFINITY); // true
System.out.println(-2.0 / 0 == Double.NEGATIVE_INFINITY); // true
System.out.println(-1 / 0.0 == Double.NEGATIVE_INFINITY); // true
System.out.println(-2 / 0.0 == Double.NEGATIVE_INFINITY); // true
System.out.println(new Double(Double.NEGATIVE_INFINITY) == Double.NEGATIVE_INFINITY); // true

// 需特别注意的特殊case:
System.out.println(1.0 / -0 == Double.POSITIVE_INFINITY); // -0和0没有区别,所以结果是POSITIVE(true)
System.out.println(1.0 / -0.0 == Double.NEGATIVE_INFINITY); // -0.0和0.0是有区别的,所以结果是POSITIVE(false)
}

总结一下:浮点数除法运算,分母为0且分子不为0,结果就是POSITIVE_INFINITY/NEGATIVE_INFINITY。

Tips:它哥两分别称作正无穷大和负无穷大

🚀isNaN(double v)

public static final double NaN = 0.0d / 0.0;

public static boolean isNaN(double v) {
return (v != v);
}

what?自己还能不等于自己?bug吧~

来看看:

@Test
public void fun3() {
// double d = 0.0d / 0; // 结果一样

System.out.println(d == Double.NaN);
System.out.println(Double.isNaN(d));
}

运行后的输出结果为:

false
false -> d==d这个是false哟
true

惊不惊喜,意不意外:还真存在自己不等于自己的情况呢。

总结一下:浮点数除法计算,分母为0且分子为0,结果就是NaN。并且:每次计算的NaN都永不相等。

Tips:NaN代表不是数字,因此“不是数字”和“不是数字”不相等,从逻辑上好像也说得通嘛

🌈针对此异常的补充说明

围绕POSITIVE_INFINITY、NEGATIVE_INFINITY、NaN三个常量进行一些补充说明吧。

🚀直接打印输出什么?

@Test
public void fun4() {
System.out.println(Double.POSITIVE_INFINITY);
System.out.println(Double.NEGATIVE_INFINITY);
System.out.println(Double.NaN);
}

运行程序,输出:

Infinity
-Infinity
NaN

总结一下:Double对象打印输出(toString或者序列化),不一定永远是数字,也有可能是字符串。

🚀 是否可以参与运算和比较?

虽然是常量,但毕竟也是数字类型嘛,那就看看运算和比较喽:

运算:

@Test
public void fun7() {
System.out.println("正无穷大参与运算:" + (Double.POSITIVE_INFINITY + 1)); // Infinity
System.out.println("正无穷大参与运算:" + (Double.POSITIVE_INFINITY - 1)); // Infinity
System.out.println("负无穷大参与运算:" + (Double.NEGATIVE_INFINITY * 1)); // -Infinity
System.out.println("负无穷大参与运算:" + (Double.NEGATIVE_INFINITY / 1)); // -Infinity
System.out.println("负无穷大参与运算:" + (Double.NEGATIVE_INFINITY / 0)); // -Infinity

System.out.println("NaN参与运算:" + (Double.NaN + 1)); // NaN
System.out.println("NaN参与运算:" + (Double.NaN - 1)); // NaN
System.out.println("NaN参与运算:" + (Double.NaN * 1)); // NaN
System.out.println("NaN参与运算:" + (Double.NaN / 1)); // NaN
System.out.println("NaN参与运算:" + (Double.NaN / 0)); // NaN

// 特殊场景
System.out.println(Double.POSITIVE_INFINITY - Double.POSITIVE_INFINITY); // NaN
System.out.println(Double.NEGATIVE_INFINITY - Double.NEGATIVE_INFINITY); // NaN
System.out.println(Double.POSITIVE_INFINITY + Double.NEGATIVE_INFINITY); // NaN

System.out.println("负无穷大参与运算:" + (Double.POSITIVE_INFINITY / -0.0)); // -Infinity
System.out.println("负无穷大参与运算:" + (Double.NEGATIVE_INFINITY / -0.0)); // Infinity
}

总结一下:正/负无穷大和任何数值(包括除以0)做运算结果都是本身,和Infinite or NaN运算结果为NaN;NaN进行任何运算的结果都是NaN。

特例:正/负无穷大若除以-0的话,结果互调

比较:

@Test
public void fun6() {
System.out.println("正无穷大 > 任何数吗? -> " + (Double.POSITIVE_INFINITY > Double.MAX_VALUE)); // true
System.out.println("正无穷大 > 任何数吗? -> " + (Double.POSITIVE_INFINITY > Long.MAX_VALUE)); // true
System.out.println("负无穷大 < 任何数吗? -> " + (Double.POSITIVE_INFINITY > Double.MIN_VALUE)); // true
System.out.println("负无穷大 < 任何数吗? -> " + (Double.POSITIVE_INFINITY > Long.MIN_VALUE)); // true

System.out.println("NaN参与比较:" + (Double.NaN == Double.NaN)); // false
System.out.println("NaN参与比较:" + (Double.NaN > Double.NaN)); // false
System.out.println("NaN参与比较:" + (Double.NaN < Double.NaN)); // false
System.out.println("NaN参与比较:" + (Double.NaN < 1)); // false
System.out.println("NaN参与比较:" + (Double.NaN < -1)); // false

System.out.println("NaN参与比较:" + (Double.NaN != -1)); // true
System.out.println("NaN参与比较:" + (Double.NaN != Double.NaN)); // true
}

总结一下:正无穷大比任何数值都大;负无穷大比任何数值都小;NaN参与!=比较永远是true(包括和自己比),除此之外都为false。

🚀 Float里的这三个常量和Double一样吗?

弱弱问一句:2023年了在实际业务开发中,不会真有人使用Float吧?吧?吧?

灵魂拷问:如果你使用了Float,收益是什么?是否真的值得?

Float类里也存在这三个常量和判断的方法:

public final class Float extends Number implements Comparable<Float> {

// 常量
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
public static final float NaN = 0.0f / 0.0f;

public static boolean isInfinite(float v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}
public static boolean isNaN(float v) {
return (v != v);
}

}

和Double可谓一毛一样嘛。看下这个:

@Test
public void fun5() {
System.out.println(Double.POSITIVE_INFINITY == Float.POSITIVE_INFINITY);
System.out.println(Double.NEGATIVE_INFINITY == Float.NEGATIVE_INFINITY);
System.out.println(Double.NaN == Float.NaN);
}

运行程序,输出:

true
true
false

结论无需多言,自行体会做到心中有数哈。

🚀 其它语言的表现

以弱类型语言JavaScript为例:

图片

表现和Java一样。毕竟NaN早已被纳入IEEE 754规范了,不出意外每种编程语言的表现都是一致的。

Tips:JavaScript中的isFinite()方法是正向思维的,和Java里isInfinite()是“反”着来的哦

🌈遇到此异常怎么破?

解决问题的难度永远在根因定位上,至于遇到此异常怎么破嘛,略!!!

考虑到代码的健壮性,实际场景中是可以对这些异常做预处理的:使用Double.isNaN()、Double.isInfinite()等方法来做分支逻辑

🍞总结

在Java中,浮点数0并非一个准确值,而是一个无限接近0的数。为此才闹出这么多令人费解的“幺蛾子”,这是由计算机底层原理决定的,记住就好,无解。

计算机的运算基于数学,但貌似也有些“不同于”数学理论。这不,NaN这玩意就是这么神奇的存在。

责任编辑:武晓燕 来源: YourBatman
相关推荐

2014-05-16 13:44:27

2015-07-27 11:35:15

2016-05-04 11:19:53

2015-02-01 15:52:27

2013-10-15 15:54:46

Windows XPWindows 7

2021-02-09 08:31:38

线下环境 stable

2020-12-15 10:14:47

NumPynanPython

2013-09-17 10:16:50

Infinite Mo傻瓜式

2018-08-22 06:33:30

2022-08-10 14:52:02

DeepFakeAI

2018-03-15 10:36:30

2009-07-08 12:53:29

JDK源码Java.lang.B

2022-07-18 13:35:24

数据安全网络安全

2018-06-29 15:45:29

技术总监管理者架构设计

2017-05-23 15:23:08

金融云

2022-10-30 14:34:30

数据业务

2021-12-03 20:34:03

计算

2017-03-22 12:13:36

AI神经网络模型算法

2020-11-09 06:00:04

Windows 10Windows操作系统
点赞
收藏

51CTO技术栈公众号