文 | 杨魁 on 前端 

在查看本文之前,请先思考两个问题。

  1. typeof1/undefined 是多少

  2. [1,2,NaN].indexOf(NaN) 输出什么

如果你还不确定这两题的答案的话,请仔细阅读本文。 这两题的答案不会直接解释,请从文章中寻找答案。

一、NaN 的本质

我们知道 NaN(Not A Number) 会出现在任何不符合实数领域内计算规则的场景下。比如 Math.sqrt(-1)就是 NaN,而 1/0 就不是 NaN。前者属于复数的范畴,而后者属于实数的范围。

同时需要注意的是,NaN 只会出现在浮点类型中,而不会出现在 int 类型里(当然 JS 并没有这个概念)

什么意思?用你熟悉的任何支持 int 和 double 两种类型的语言(比如 C)。在保证它不会偷偷做隐式类型转换的情况下,分别用 int 和 double 打印出 sqrt(-1), 你就能发现只有在 double 的类型下才能看到 NaN 出现,而 int 呢?编译器甚至会给你一个 Warning。

那么在浮点数下是如何表示一个 NaN 的呢?为了方便,下面用单精度 float 来表示,请看下图。

Under the Hood: NaN of JS_Java在 3b 情况中,NaN 得满足:从左到右,以 1 开始,不关心第 1 位的值,第 2 位到第 9 位都是 1,剩下的位不全 为 0。 关于 浮点数内部的组成,这里不做具体的介绍,我们只需要了解到浮点数分为 3 个部分就可以:

  1. 符号位

  2. 指数位

  3. 精度位

其中 float 的指数位有 8 位,精度位有 32 - 1 - 8 = 23 位 double 的指数位有 11 位,精度位有 64 - 1 - 11 = 52 位 所以上面 NaN 的满足条件,可以看成:精度位不全为 0,指数位全 1 就可以了。

所以按上面的说法, 0x7f81111,0x7fcccccc 等等这些都符合 NaN 的要求了。我们可以尝试一下,自己写一个函数,用来往 8 个字节的内存的前两个字节写入全 1. 也就是连续 16 个 1,这就符合 NaN 的定义了。看下面这段代码:

double createNaN() {  unsigned char *bits = calloc(sizeof(double), 1);  // 大部分人的电脑是小端,所以要从 6 和 7 开始,而不是 0 和 1  // 不清楚概念的可以参考阮老师:  // [理解字节序 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2016/11/byte-order.html)  bits[6] = 255;  bits[7] = 255;  unsigned char *start = bits;  double nan = *(double *)(bits);  output(nan);  free(bits);  return nan;}

其中 output 是一个封装,用来输出任意一个 double 的内部二进制表示。详细代码查看 gist。 最后我们得到了:Under the Hood: NaN of JS_Java_02

看来创造一个 NaN 不是很难,对吧? 同样的,为了证明上面的图的正确性,再看看 Infinity 的内部结构是否符合Under the Hood: NaN of JS_Java_03


两种 NaN

如果再细分的话,NaN 还可分为两种:

  1. Quiet NaN

  2. Signaling NaN

从性质上,可以认为第一种 NaN 属于“脾气比较好”,比较“文静”的一种,你甚至可以直接定义它,并使用它。 比如我们在 JS 中可以使用类似于 NaN+1,NaN+'123' 的操作,还不会报错。

而 Signaling NaN 就是一个“爆脾气”。如果你想直接操作它的话,会抛出一个异常(或者称为 Trap)。也就不允许 NaN + 1 这种操作了。像这种不好惹的 NaN,根据 WiKi 中的介绍,它可以被用来:

Filling uninitialized memory with signaling NaNs would produce the invalid operation exception if the data is used before it is initialized Using an sNaN as a placeholder for a more complicated object , such as: A representation of a number that has underflowedA representation of a number that has overflowedNumber in a higher precision format A complex number

二、NaN != NaN

如果换个角度理解,因为 NaN 的表示方式实在太多,仅仅在 float 类型中,就有 2^(32-8) 中情况,所以 NaN 碰到一个和它二进制表示一模一样的概率实在太低了,所以我们可以认为 NaN 不等于 NaN