JavaScript 中 Number 到底有多少个?为了弄清这个问题,并且了解 JavaScript 的 Number 类型设计,将从规范上进行探究。

这个问题的来源是 winter 老师的 #重学前端# 课程,在讲解 Number 类型时提到这样一句话:

JavaScript 中的 Number 类型有 18437736874454810627(即 2^64 - 2^53 + 3) 个值。

对于这个 2^64 - 2^53 + 3,winter老师并没有给出相关的解释,为了弄清楚这个的来源去查阅相关资料后进行整理。

为了知道这个问题的答案,需要先了解一下在JavaScript中Number是如何表示的。

JavaScript中的Number

从ECMAScript规范中得知, Number的值是:

primitive value corresponding to a double-precision 64-bit binary format IEEE 754-2008 value

意思就是:符合IEEE 754-2008 格式的 64 位双精度二进制的原始值。

在 IEEE 754 中,64位双精度浮点数中的64位由以下部分组成:

64位双精度浮点数

  • s - 符号位,占 1 bit (数符)
  • e - 指数位,占 11 bit (阶码=阶码真值+偏移量)
  • f - 小数位,占 52 bit (尾数)

在规范中也明确的给出了各种情况的计算方式:

IEEE 754计算

  1. 如果指数位e全为1,即 e = 11111111111,也就是十进制的2047(2^11-1),且小数位f不为0,就超过了最大值,这样的数字无法正确表示,表示为 NaN(Not a Number),此时会无视作为符号位的s

  2. 如果指数为e全为1,但小数位f为0的话,则根据符号位s得到两个特殊的值((-1)^s),当s为0时,得到正无穷大,对于JS中的Infinity;当s为1时,得到负无穷大,对应-Infinity

  3. 如果指数e在 0<e<2^11-1 之间,这个表示 IEEE 认为的正常的浮点数区间,那么对应的数字的计算公式为:(-1)sx(2e-1023)x(1.f)
    对于e-1023和1.f可能会有些疑惑。其中 1023 代表指数的偏移量,那么为什么会有这个偏移量呢,首先,作为11位的阶码,如果是无符号,那么表示的范围为 [0-2047], 当处于我们第三种情况时,e的范围要去掉0和2047,变成 [1,2046],如果要表示正负,则需要使用一个bit去作为符号位,范围为 [-1022, 1023],如果没有偏移量,那么需要引入补码,计算更加复杂,为了简化运算,则使用无符号的阶码,引入了偏移量的概念,通过偏移将其转换成 [1,2046],所以偏移量为 1023。
    那么对于1.f来说,因为对于浮点数,在规范中采用科学计数法的方法,对于十进制12.34来说,用科学技术法表示为 1.234x10^2,不会表示为 0.1234x10^3,其首位肯定是一个不为0的数字,那么在二进制中,只有0和1,那么他的首位就只能是1(用科学计数法表示,首位不能为0)。因为对于所有的浮点数他的首位都是1,因此可以省略一位进行,不需要额外占用1位,所以f实际上的有效位数有53位(其中首位是1)。在计算时需要使用上这个隐含的1。这也是1.f的由来

  4. 当e为0且f不为0时,是非规范化的数字,用于表示那些非常接近0的数字。

    非规格化下,指数为,000 00000000 - 011 11111111 (偏移量) = - 100 00000001(转成10进制,减1取反)= - 1023
    从指数看,得知最小值是2^-1023,然而如果尾数是0.00000000 00000000 00000000 00000000 00000000 00000000 0001 不为0的情况,52位尾数相当于小数点还能虚拟化的向右移动51,可以取得更小的 2^-51, 所以最小值为 2^-1074 = Math.pow(2,-1074) 约等于 5e-324
    而JS最小值常量Number.MIN_VALUE正是5e-324
    所以(-5e-324,5e-324)之间的数比可表示的最小数还要小,显示成0,叫反向溢出。

  5. 如果指数是0且尾数也是0,则表示正负0的情况

解释

从上面的分类我们得知,前两种是非正常的值,第一种含有 2^52 种情况(尾数有52位),第二种含有两种情况(正无穷和负无穷),那么根据排列组合,共有 2*1*2^52 = 2^53 种情况。这些非正常的值在JS中以 NaN、+Infinity、-Infinity表示,因此在Number中多了三种情况。那么总数就是 2^64-2^53+3,算得有 18437736874454810627 个值。

其中也可以解释为什么 NaN != NaN,因为 NaN 代表了 2^52 种情况,不是一个固定的值,而是一批值,自然也不会等于自身了。