5. 四类常量
以 JavaScript 语言里的 Number 数据类型为例
在 JavaScript 里,Number 对象有四类静态属性,分别是:
最大/最小正数
能表示的最大正数:
Number.MAX_VALUE
能表示的最小正数(最接近 0 的正数):
Number.MIN_VALUE
最大/最小安全整数
最大安全整数 ,
Number.MAX_SAFE_INTEGER
最小安全整数 ,
Number.MIN_SAFE_INTEGER
最小间隔:能表示的数值之间的最小间隔,
Number.EPSILON
特殊值
正负无穷大(上溢时返回)
Number.NEGATIVE_INFINITY
Number.POSITIVE_INFINITY
非数值(Not a Number),
Number.NaN
±0
它们的值分别是:
结合 Number 在内存中的存储方式,如何理解这些常量的含义和值呢?
符号位,1 位,0 正 1 负
指数,11 位,全 0 和全 1 是为特殊数值保留的
有效数,52 个显式存储位 + 1 个默认前导位,共 53 位
1. 最大正数和最小正数
1.1 最大正数
最大正数 MAX_VALUE
应该是:正数即符号位为 0,11 位指数是“全 1-1”,52 位有效数是“全 1”。如下:
对应的 binary64 内存存储就是 0x7FEFFFFFFFFFFFFF
。
真实值就是:
=
=
=
=
= 1.7976931348623157e+308
≈ 1.8e+308
1.2 最小正数
最小正数 MIN_VALUE
应该是:正数即符号位为 0,11 位指数是“全 0”,52 位有效数是“全 0+1”。此时即为次正规数,如下:
对应的 binary64 内存存储就是 0x0000000000000001
。
真实值就是:
=
=
= 5e-324
2. 最大安全整数和最小安全整数
在介绍最大安全整数和最小安全整数之前,我们先来认识下什么是安全整数。
2.1 安全整数
安全整数是形容数学概念上的一个整数在 JavaScript 里的表示方式。如果说一个整数是安全的,就意味着它能在 JavaScript 中被唯一地表示。
The idea of a safe integer is about how mathematical integers are represented in JavaScript. In the range (−253, 253) (excluding the lower and upper bounds), JavaScript integers are safe: there is a one-to-one mapping between mathematical integers and their representations in JavaScript.
那么,如何理解“被唯一”地表示呢?来看个例子。
当 IEEE 754 binary64 的 53 个有效位全是 1 且都处于整数位置时,即实际指数值是 52,偏正指数值是 52+1023 = 1075 = ,此时的内存表示如下:
真实值就是:
=
=
=
= 9007199254740991
当它加 1 时,值会变成 = = 1.00...000 * = 1.00...000 * 。注意,末位的 0 之所以被删,是因为内存里的 IEEE 754 binary64 格式的有效位只有 53 个(包括默认的前导位 1),所以需要裁切精度。
当再加 1 时,值会变成 = = 1.00...001 * ≈ 1.00...011 * = 1.00...01 * 。红色的 1 表示即将被裁掉的精度位,11 表示舍掉了 1 之后进位的 1(即四舍五入式)。
为了方便阅读,我们用表格的形式来描述。十进制那列是从 即 9007199254740991 开始的,每行的值依次加 1。如下:
9007199254740991
1.11...11 *
1.11...11 *
9007199254740992
1.000...00 *
1.00...000 *
9007199254740993
1.000...01 *
1.00...011 *
9007199254740994
1.000...10 *
1.00...010 *
9007199254740995
1.000...11 *
1.00...101 *
9007199254740996
1.00...100 *
1.00...100 *
9007199254740997
1.00...101 *
1.00...111 *
9007199254740998
1.00...110 *
1.00...110 *
...
说明:“二进制科学记数法”列中的标红数字,是在存入内存时将会被裁掉的部分,因为小数点后只能存储 52 个有效数字。“IEEE 754 binary64”列中,标红的数字即前一列的内容,标绿的数字就是用了四舍五入(即 0 舍 1 入)的舍入方式裁剪精度后最终存储的有效数字。
我们可以很直观地看到,当二进制有效数的位数大于 53 位时,就得舍弃最后一位,这会导致值不同的两个数值会有相同的 IEEE 754 binary64 表示。这,就是没有“被唯一”地表示。
当指数是 53 时,是 2 个整数共用一个内存存储,因为会舍去末位的 53-52 = 1 位
当指数是 54 时,是 4 个整数共用一个内存储存,因为会舍去末位的 54-52 = 2 位
当指数是 55 时,是 8 个整数共用一个内存储存,因为会舍去末位的 55-52 = 3 位
当指数是 56 时,是 16 个整数共用一个内存储存,因为会舍去末位的 56-52 = 4 位
...
注意:如果是用向 0 舍入的方式(即直接截断),那么共用一个内存存储的整数范围会有所不同,不过不影响大局。
JavaScript 里的安全整数的范围是 [, ],没有包含边界 。虽然 在内存中是能被唯一表示的,但是考虑到如果是用直接截断的方式,它是会和 重叠的。所以在 JavaScript 中,一旦精度有被截断,就会被视为是不安全的。如下:
所以,在 JavaScript 中,说一个整数是安全的,就意味着它能唯一地表示一个数学意义上的整数,且在存储时没有被舍弃精度。
2.2 最大安全整数
最大安全整数 MAX_SAFE_INTEGER
应该是:正数即符号位为 0,指数的实际值是 52(即内存值为 52+1023 = 1075 = 10000110011(2) ),52 位有效数是“全 1”。如下:
对应的 binary64 内存存储就是 0x433FFFFFFFFFFFFF
。
真实值就是:
=
=
= -1
= 9007199254740991
2.3 最小安全整数
最小安全整数 MIN_SAFE_INTEGER
除了符号和最大安全整数 MAX_SAFE_INTEGER
不同之外,其余都一样,即:负数即符号位为 1,指数的实际值是 52(即内存值为 52+1023 = 1075 = ),52 位有效数是“全 1”。如下:
对应的 binary64 内存存储就是 0xC33FFFFFFFFFFFFF
。
真实值就是:
=
=
=
= -9007199254740991
3. 最小间隔
3.1 不均匀的断层
在《精度损失》里曾提到浮点数到实数的映射,其中间有覆盖不到的小断层,如下图:
能表示的两个数字之间的“间隔”,就是图中的那些个“小断层”。那么,这些断层是均匀的吗?如果均匀,值是多少?如果不均匀,最小值和最大值分别是多少?
要回答这个问题,我们就得看看二进制到十进制的转换了。在《为什么 toPrecision(17) 能让内存中的数值原形毕露?》里我们是将整数部分和小数部分分开来讨论的,相关结论是:
整数部分,当有 n 个二进制有效位时,可以表示 个十进制数,且它们是个等差数列,差值是 1
小数部分,当有 n 个二进制有效位时,可以表示 个十进制数,且它们是个等差数列,差值是 ,数列范围是 [, 1)
在真实的 IEEE 754 binary64 存储中,二进制的那 53 个有效位是可以同时有整数部分和小数部分的,如果再考虑指数,这会让每个有效位上的数字权重(即 2 的几次方)变成流动的。所以显然,两个能表示的数之间的“间隔”是不均匀的。
那间隔的最小值和最大值分别是多少?
3.2 最小间隔和最大间隔
假设现在有 4 个二进制有效位,其中包括 1 个默认前导位和 3 个显式存储位。当默认前导位是 0 时是次正规数,当默认前导位是 1 时是正规数。我们看看在这种情况下,二进制到十进制是一个什么样的对应关系。如下:
0.xxx
0.000 0.001 0.010 0.011 0.100 0.101 0.110 0.111
0 0.125 0.25 0.375 0.5 0.625 0.75 0.875
0.125 =
1.xxx
1.000 1.001 1.010 1.011 1.100 1.101 1.110 1.111
1 1.125 1.25 1.375 1.5 1.625 1.75 1.875
0.125 =
1x.xx
10.00 10.01 10.10 10.11 11.00 11.01 11.10 11.11
2 2.25 2.5 2.75 3 3.25 3.5 3.75
0.25 =
1xx.x
100.0 100.1 101.0 101.1 110.0 110.1 111.0 111.1
4 4.5 5 5.5 6 6.5 7 7.5
0.5 =
1xxx
1000 1001 1010 1011 1100 1101 1110 1111
8 9 10 11 12 13 14 15
1 =
同理,当我们有 53 个二进制有效位时(1 个默认前导位 + 52 个显式位),从 0 ~ 1 ~ ~ ~ ~ ~ ... ~ ( - 1) 之间,每段都是连续的等差数列,只是差值不同而已。其中,最小的差值是 ,最大的差值是 = 1。
之所以只考虑了次正规数和 0 ≤ 指数实际值 ≤ 52 的情况,是因为当指数过大或者过小时,能表示的数值就不是线性连续的了。感兴趣的小伙伴们,可以自行推导下。
在数轴上,二进制和十进制的逻辑类似。
综上,最小间隔 EPSILON
就是能表示的等差数列的最小差值,即 = 2.220446049250313e-16
4. 特殊值
包括次正规数,还有三类特殊值,它们都是依据指数的特殊编码来区分的。详情可查看《双精度浮点数 / 内存存储 / 指数位编码》,这里就不展开说了。
4.1 正负无穷大
这两个就是指数位“全 1”的特殊情况(之一):指数位是“全 1”,有效数位是“全 0”。
正无穷大的 binary64 的内存存储为 0x7FF0000000000000
,如下图:
负无穷大的 binary64 的内存存储是 0xFFF0000000000000
,如下图:
4.2 非数值
NaN
,Not a Number,是指数位“全 1”的特殊情况(之一):指数位是“全 1”,只要有效数位不是“全0”的都是 NaN
(忽略符号位)。如下图:
从 0x7FF0000000000001
~ 0x7FFFFFFFFFFFFFFF
都是 NaN
。
4.3 ±0
0 有两种表示方式:-0 和 +0(0 是 +0 的别名)。在实际开发中,这几乎没啥影响。但需要注意一点,就是当 0 是被除数的时候,会有不同:
+0 的 IEEE 754 binary64 内存存储为:
-0 的 IEEE 754 binary64 内存存储为:
5. 总结
这部分以 JavaScript 语言里的数值类型 Number 为例,介绍了双精度浮点数里的四类常量,对它们知其然也知其所以然会对编程大有裨益。
能表示的最大正数和最小正数
能表示的最大安全整数和最小安全整数
能表示的数值之间的间隔
浮点数→实数:有间隔,且间隔不均匀
在线性增长中,是 n 段等差数列。最小差值/间隔是
Number.EPSILON
,最大差值/间隔是 1在指数增长中,安全整数范围内的数列是连续的,之外的是不连续的(有重叠,没被唯一表示)
特殊值:
NaN
, ±Infinity
, ±0
6. 主要参考
Last updated