Java 中的 NaN

概述

简单来说可以认为 NaN 是一个数字数据类型变量值,这个类型变量被定义为 这不是一个数字

在这篇文章中,我们对 Java 中的 NaN 进行一些简单的描述和说明和在那些操作的过程中可以尝试这个值,和可以如何去避免。

什么是 NaN

NaN 通常表示一个无效的操作结果。 例如,你尝试将数字 0 去除以 0,这个在数学中是不存在的,同时在 Java 中定义 NaN 也确实就是通过这个不存在的操作来定义的。

我们通常也使用 NaN 来表示不能显示的变量值。 例如,我们对数字 -1 开平方根的时候,也是这种情况。

例如我们可以只在负数的情况下描述 value (i) 。

在页面 IEEE 754 - 维基百科,自由的百科全书 中对非数值 NaN 的定义进行了说明。你可以阅读上面的文章来了解更多有关 NaN 的定义。

在 Java 中,只有浮点数据类型 floatdouble 实现了这个标准。

Java 咋 使用 Float.NaNDouble.NaN 来定义了 NaN 构造函数。

在 double 中一个常量 Not-a-Number (NaN) 定义了这个值,这个值等于 Double.longBitsToDouble(0x7ff8000000000000L) 的返回值。

在 float 中一个常量 Not-a-Number (NaN) 定义了这个值,这个值等于 Float.intBitsToFloat(0x7fc00000) 的返回值。

在 Java 中没有针对其他数据类型定义的 NaN 了。

NaN 的比较

在 Java 中,如果我们开始写一个方法的时候,我们应该需要针对方法的输入数据进行检查,以确保输入数据的准确和输入数据在允许的范围内。

NaN 在绝大部分情况下都不是一个有效的输入参数,因此在 Java 的方法中,我需要对输入的参数进行比较,以确保输入的参数中的值不是 NaN,然后我们能够对输入参数进行正确的处理。

NaN 不能余任何浮点类型数据进行比较,这就表示,任何有 NaN 参与的比较都会返回 false(这里只有一个例外为 “!=” 将会返回 true)。

我们将会得到 针对 x != x 表达式,如果 x 是 NaN 的话,我们将会在这里得到 true。

考察下面的代码:

System.out.println("NaN == 1 = " + (NAN == 1));
System.out.println("NaN > 1 = " + (NAN > 1));
System.out.println("NaN < 1 = " + (NAN < 1));
System.out.println("NaN != 1 = " + (NAN != 1));
System.out.println("NaN == NaN = " + (NAN == NAN));
System.out.println("NaN > NaN = " + (NAN > NAN));
System.out.println("NaN < NaN = " + (NAN < NAN));
System.out.println("NaN != NaN = " + (NAN != NAN));

下面的内容就是针对上面代码的输出结果。

NaN == 1 = false
NaN > 1 = false
NaN < 1 = false
NaN != 1 = true
NaN == NaN = false
NaN > NaN = false
NaN < NaN = false
NaN != NaN = true

所以,我们不能够通过比较来检查数据是不是 NaN。

事实上,我们也不应该用 “==” 或 “!= “ 来对 double 或者 flat 类型的数据进行比较。

所以,我们可以使用 “x != x”* 表达式来检查 NaN 是不是为 true。

更多的,我们可能会使用 Float.isNaNDouble.isNaN 方法来检查这个输入的参数值是不是 NaN。
实际上,这种方法更好,因为这能够让代码更加易读。

考察下面的代码:

double x = 1;
System.out.println(x + " is NaN = " + (x != x));
System.out.println(x + " is NaN = " + (Double.isNaN(x)));
        
x = Double.NaN;
System.out.println(x + " is NaN = " + (x != x));
System.out.println(x + " is NaN = " + (Double.isNaN(x)));

下面内容就是上面代码的输出。

1.0 is NaN = false
1.0 is NaN = false
NaN is NaN = true
NaN is NaN = true

产生 NaN 的操作

当我们对 floatdouble 类型进行操作和计算的时候,我们应该注意某些操作是可能会产生 NaN 值的。

一些针对浮点计算的方法和操作是会产生 NaN 这个值来替换掉可能抛出的异常,换句话说就是有些操作不会抛出异常,但是返回的结果是 NaN。

最常见的情况就是对数字进行计算的时候,这个算法在数学中还没有被定义,或者被定义是不可以这样做的。
如最常见的 0 除以 0 的情况。

因为在数学中,这种情况被定义为非法的。

        System.out.println("Undefined Operations Produce NaN");
        final double ZERO = 0;
        System.out.println("ZERO / ZERO = " + (ZERO / ZERO));
        System.out.println("INFINITY - INFINITY = " + (Double.POSITIVE_INFINITY - Double.POSITIVE_INFINITY));
        System.out.println("INFINITY * ZERO = " + (Double.POSITIVE_INFINITY * ZERO));
        System.out.println();

上面的代码将会有下面的输出:

Undefined Operations Produce NaN
ZERO / ZERO = NaN
INFINITY - INFINITY = NaN
INFINITY * ZERO = NaN

同时有关数字的操作和计算的结果并不能产生数字的情况下也会输出为 NaN。

        System.out.println("Operations with no real results produce NaN");
        System.out.println("SQUARE ROOT OF -1 = " + Math.sqrt(-1));
        System.out.println("LOG OF -1 = " + Math.log(-1));
        System.out.println();

上面的代码的结果如下:

Operations with no real results produce NaN
SQUARE ROOT OF -1 = NaN
LOG OF -1 = NaN

所有数字与有 NaN 参与的计算的结果也会返回 NaN:

        System.out.println("Operations with NaN produce NaN");
        System.out.println("2 + NaN = " + (2 + Double.NaN));
        System.out.println("2 - NaN = " + (2 - Double.NaN));
        System.out.println("2 * NaN = " + (2 * Double.NaN));
        System.out.println("2 / NaN = " + (2 / Double.NaN));
        System.out.println();

上面的diam将会输出为:

Operations with NaN produce NaN
2 + NaN = NaN
2 - NaN = NaN
2 * NaN = NaN
2 / NaN = NaN

最后,我们知道我们不能够给 double 或者 float 指派为 null 对象类型。

作为另外一种解决方案,我们可以为 double 或者 float 指派 NaN 数值来表示丢失或者未知的值:

如下面的代码:

double maxValue = Double.NaN;

结论

在本篇文章中,我们对 NaN 的情况进行了一些简单的讨论,同时我们也讨论了在实际的计算中可能会有哪些情况会导致产生 NaN,同时对如何进行 NaN 在 Java 中的比较和计算也提供了一些实例。

欢迎大家参与讨论。