Open anjia opened 2 years ago
toPrecision(17)
能让内存中的数值原形毕露?通过上篇文章 #87 ,我们知道了双精度浮点数之所以会“损失精度”的根本原因。本文将在此基础上,继续探索编程语言(以 JavaScript 为例)是如何读取并显示内存中的 binary64 数据的。
继续以十进制的 0.1 为例,我们知道了它在内存中存储的真实数值并不是 0.1 而是 0.100000000000000005551115123126。
0.1 的 IEEE 754 binary64 表示
但是,在我们的日常开发中,0.1 总会输出 0.1 而不是它在内存中的精确值。比如:
0.1; // 0.1
Number(0.1); // 0.1
除非我们手动指定精度。如下:
Number(0.1).toPrecision(); // '0.1', 参数为空则调 toString()
Number(0.1).toPrecision(16); // '0.1000000000000000' = 0.1
Number(0.1).toPrecision(17); // '0.10000000000000001' > 0.1
Number(0.1).toPrecision(18); // '0.100000000000000006'
Number(0.1).toPrecision(19); // '0.1000000000000000056'
Number(0.1).toPrecision(20); // '0.10000000000000000555'
Number(0.1).toPrecision(21); // '0.100000000000000005551'
Number(0.1).toPrecision(55); // '0.1000000000000000055511151231257827021181583404541015625'
Number(0.1).toPrecision(70); // '0.1000000000000000055511151231257827021181583404541015625000000000000000'
说明:
toPrecision()
方法返回的类型是字符串
- 若精度参数为空/未定义,则会调用 Number.toString() 方法
- 若精度不在 [1,100] 之间,则会抛出错误 RangeError
- ECMA-262 只需要最多 21 位有效数字的精度
也就是说,在我们的日常使用中,浏览器会自动帮我们截取精度,以让显示的数值“看起来”是正确的。
那么,双精度浮点数是按照什么规则来截取精度值的呢?维基百科里这样写道:
The 53-bit significand precision gives from 15 to 17 significant decimal digits precision (2−53 ≈ 1.11 × 10−16). If a decimal string with at most 15 significant digits is converted to IEEE 754 double-precision representation, and then converted back to a decimal string with the same number of digits, the final result should match the original string. If an IEEE 754 double-precision number is converted to a decimal string with at least 17 significant digits, and then converted back to double-precision representation, the final result must match the original number.
IEEE 754 binary64 里的 53 位有效数精度能提供十进制数的 15 到 17 个有效数精度。
- 如果将一个最多有 15 位有效数字的十进制字符串转成 IEEE 754 双精度表示,然后再转回有相同位数的十进制字符串,则最终的结果应该(should)会和原始字符串相匹配。
- 如果将一个 IEEE 754 双精度数值转成一个至少有 17 位有效数字的十进制字符串,然后再将它转回双精度表示,则最终的结果一定(must)会和原始数值相匹配。
大意就是:
本文旨在讨论“JavaScript 是如何读取并显示内存中的 binary64 数据”的,所以我们只重点关注第 2 点,即:读内存中的数值时,只要十进制的有效数字是 17 个就能保证不出错(内存里存啥就显示啥)。
再来看几个例子感受下。
Number(0.2); // 0.2
Number(0.2).toPrecision(16); // '0.2000000000000000' = 0.2
Number(0.2).toPrecision(17); // '0.20000000000000001' > 0.2
Number(1.005); // 1.005
Number(1.005).toPrecision(16); // '1.005000000000000' = 1.005
Number(1.005).toPrecision(17); // '1.0049999999999999' < 1.005
那么,为什么“16”就能刚好让数值看起来是正确的,而“17”就是一个照妖镜呢?
在回答这个问题之前,我们先看下二进制的 n 个有效位表示成十进制是什么样子的。
为了更清晰地描述问题,我们将“纯整数”和“纯小数”分开讨论。
当只有 1 个二进制有效位时 x
:能表示 2 个十进制数,它们之间的差值是 1。如下:
二进制 | 十进制 |
---|---|
0 | 0 |
1 | 1 |
当有 2 个二进制有效位时 xx
:能表示 22 = 4 个十进制数,它们之间的差值是 1。如下:
二进制 | 十进制 |
---|---|
00 | 0 |
01 | 1 |
10* | 2* |
11* | 3* |
* 表示多了一个有效位之后新增的数,新增了 2(10)个,其高位均是 1(2)
当有 3 个二进制有效位时 xxx
:能表示 23 = 8 个十进制数,它们之间的差值是 1。如下:
二进制 | 十进制 |
---|---|
000 | 0 |
001 | 1 |
010 | 2 |
011 | 3 |
100* | 4* |
101* | 5* |
110* | 6* |
111* | 7* |
* 表示多了一个有效位之后新增的数,新增了 4(10) = 22 个,其高位均是 1(2)
综上,利用数学归纳法可得出结论:
当只有 1 个二进制有效位时 0.x
:能表示 2 个十进制数,它们之间的差值是 2-1 = 0.5。如下:
二进制 | 十进制 |
---|---|
0.0 | 0 |
0.1 | 0.5 |
当有 2 个二进制有效位时 0.xx
:能表示 22 = 4 个十进制数,它们之间的差值是 2-2 = 0.25。如下:
二进制 | 十进制 |
---|---|
0.00 | 0 |
0.01* | 0.25* |
0.10 | 0.5 |
0.11* | 0.75* |
* 表示多了一个有效位之后新增的数,新增了 2(10)个,其末位均是 1(2)
当有 3 个二进制有效位时 0.xxx
:能表示 23 = 8 个十进制数,它们之间的差值是 2-3 = 0.125。如下:
二进制 | 十进制 |
---|---|
0.000 | 0 |
0.001* | 0.125* |
0.010 | 0.25 |
0.011* | 0.375* |
0.100 | 0.5 |
0.101* | 0.625* |
0.110 | 0.75 |
0.111* | 0.875* |
* 表示多了一个有效位之后新增的数,新增了 4(10) = 22 个,其末位均是 1(2)
当有 4 个二进制有效位时 0.xxxx
:能表示 24 = 16 个十进制数,它们之间的差值是 2-4 = 0.0625。如下:
二进制 | 十进制 |
---|---|
0.0000 | 0 |
0.0001* | 0.0625* |
0.0010 | 0.125 |
0.0011* | 0.1875* |
0.0100 | 0.25 |
0.0101* | 0.3125* |
0.0110 | 0.375 |
0.0111* | 0.4375* |
0.1000 | 0.5 |
0.1001* | 0.5625* |
0.1010 | 0.625 |
0.1011* | 0.6875* |
0.1100 | 0.75 |
0.1101* | 0.8125* |
0.1110 | 0.875 |
0.1111* | 0.9375* |
* 表示多了一个有效位之后新增的数,新增了 8(10) = 23 个,其末位均是 1(2)
综上,利用数学归纳法可得出结论:
以上结论,可以借鉴到 IEEE 754 双精度浮点数上就是:
接下来,让我们看看当有 n 个二进制位时,它能表示多少个十进制数,以及十进制数的有效位的情况。
二进制的不同位数,能表示的最大十进制数。如下:
二进制 | 十进制最大数 | 十进制数的范围 |
---|---|---|
1 | 21 - 1 = 1 | < 1e1 |
11 | 22 - 1 = 3 | < 1e1 |
111 | 23 - 1 = 7 | < 1e1 |
1111 | 24 - 1 = 15 | < 1e1 < 1e2 |
11111 | 25 - 1 = 31 | < 1e2 |
111111 | 26 - 1 = 63 | < 1e2 |
1111111 | 27 - 1 = 127 | < 1e2 < 1e3 |
11111111 | 28 - 1 = 255 | < 1e3 |
111111111 | 29 - 1 = 511 | < 1e3 |
1111111111 | 210 - 1 = 1023 | < 1e3 < 1e4 |
11111111111 | 211 - 1 = 2047 | < 1e4 |
111111111111 | 212 - 1 = 4095 | < 1e4 |
1111111111111 | 213 - 1 = 8191 | < 1e4 |
11111111111111 | 214 - 1 = 16383 | < 1e4 < 1e5 |
111111111111111 | 215 - 1 = 32767 | < 1e5 |
1111111111111111 | 216 - 1 = 65535 | < 1e5 |
11111111111111111 | 217 - 1 = 131071 | < 1e5 < 1e6 |
从上表可以看出,二进制的 n 个有效位可以表示十进制的 m 个有效位,用公式表示就是:
10m-1 < 2n - 1 < 10m,即 (m-1) < lg(2n - 1) < m
所以,在 IEEE 754 binary64 里,当 53 个有效位均为整数部分时,lg(253-1) < 16,即它能表示的十进制将最多有 16 个有效数字。
2 ** 53 - 1; // 9007199254740991 ≈ 9e15 < 1e16
Math.log10(2 ** 53 - 1); // 15.954589770191003 < 16
二进制的不同位数,能表示的十进制数列的差值。如下:
二进制 | 十进制差值 | 差值的范围 |
---|---|---|
0.1 | 2-1 = 0.5 = 5e-1 | > 0.1 = 1e-1 |
0.01 | 2-2 = 0.25 = 2.5e-1 | > 0.1 = 1e-1 |
0.001 | 2-3 = 0.125 = 1.25e-1 | > 0.1 = 1e-1 |
0.0001 | 2-4 = 0.0625 = 6.25e-2 | > 0.01 = 1e-2 |
0.00001 | 2-5 = 0.03125 = 3.125e-2 | > 0.01 = 1e-2 |
0.000001 | 2-6 = 0.015625 = 1.5625e-2 | > 0.01 = 1e-2 |
0.0000001 | 2-7 = 0.0078125 = 7.8125e-3 | > 0.001 = 1e-3 |
0.00000001 | 2-8 = 0.00390625 = 3.90625e-3 | > 0.001 = 1e-3 |
0.000000001 | 2-9 = 0.001953125 = 1.953125e-3 | > 0.001 = 1e-3 |
0.0000000001 | 2-10 = 0.0009765625 = 9.765625e-4 | > 0.0001 = 1e-4 |
从上表可以看出,二进制的 n 个有效位可以表示的十进制数列的差值的取值范围 10-m < 2-n,也就是说至少需要十进制的 m 个有效位。
最多需要几个呢?答案是 n 个。因为差值的小数点后有几位,能表示的十进制数列的有效数字最多就有几位。
所以,在 IEEE 754 binary64 里,当 53 个有效位均为小数部分时,2-53 ≈ -1.11e-16 > 10-16,即它能表示的十进制小数最少要有 16 个有效数字。
2 ** -53; // 1.1102230246251565e-16
而“不连续”的小数,需要再多 1 位有效数字来进行四舍五入,故小数部分需要 17 个有效数字。
本部分虽然篇幅较长,但其实就解释了一句话,就是:读内存中的 IEEE 754 binary64 数值时,只要十进制的有效数字是 17 个就能保证“内存里存啥就显示啥”。用代码表述就是 toPrecision(17)
能显示出内存中的真实值,而 toPrecision(16)
能让小数“看起来”是对的。
Number(0.1); // 0.1
Number(0.1).toPrecision(16); // '0.1000000000000000' = 0.1
Number(0.1).toPrecision(17); // '0.10000000000000001' > 0.1
Number(0.2); // 0.2
Number(0.2).toPrecision(16); // '0.2000000000000000' = 0.2
Number(0.2).toPrecision(17); // '0.20000000000000001' > 0.2
Number(1.005); // 1.005
Number(1.005).toPrecision(16); // '1.005000000000000' = 1.005
Number(1.005).toPrecision(17); // '1.0049999999999999' < 1.005
IEEE 754 binary64 里的 53 位有效数精度能提供十进制数的 15 到 17 个有效数精度。
常规数字。如下:
10;
1024;
9007199254740991;
当以 0 开头时,需要注意:若数字有 ≥8 的,就默认是十进制的;但如果数字全是<8 的,则会被当成八进制来解析。如下:
0888; // 888
0775; // 509,会被当成八进制
科学记数法的形式,也称指数字面量。格式是beN
,其中 b 是尾数(可以是整数或浮点数),字符e/E
表示分隔符或指数指示符,N 是指数(有符号的整数)。如下:
1e1; // 10
1e-3; // 0.001
5e-324; // 5e-324
9e15; // 9000000000000000
1.7976931348623157e+308; // 1.7976931348623157e+308
2.220446049250313e-16; // 2.220446049250313e-16
字面量以 0b
或者 0B
开头,这是 ECMAScript 2015 中的新语法。如下:
0b10000111; // 135
0b11111111110; // 2046
0b10000000000; // 1024
0b01111111111; // 1023
0b10000110011; // 1075
如果 0b/0B
后面的值不是 0 和 1,会报语法错误:
0b002001111; // Uncaught SyntaxError: Invalid or unexpected token
字面量以 0o
或者 0O
开头,这是 ECMAScript 2015 中的新语法。如下:
0o755; // 493
0o644; // 420
再就是前面“十进制”里提到的,以 0 开头的情况。即便“看起来”是十进制的数字,但如果它们都 <8(即在 0\~7 之间)是会被当成八进制来解析的。如下:
0755; // 493
0775; // 509
若 0o/0O
后面的值不是 0\~7,会报语法错误:
0b6339; // Uncaught SyntaxError: Invalid or unexpected token
字面量以 0x
或者 0X
开头,后跟 0\~F。如下:
0x0010; // 16
0x7fe; // 2046
0xffff; // 65535
若 0x/0X
后面的值不是 0\~F,会报语法错误:
0x99EFG; // Uncaught SyntaxError: Invalid or unexpected token
这节内容比较直观,大约就是所见即所得。对我们的启示就是:
0b/0o/0x
最好不要省略最后简单提下,Number 类型的字面量如果都在后面加个字符 n
就是 BigInt 类型的了。如下:
755n;
9007199254740992n;
0b11111111110n; // 2046n
0o7556643555n; // 1035683693n
0xffffeeeaaan; // 1099510508202n
唯一不同的就是在 BigInt 里 0755n
不会被当成八进制,因为前缀 o/O
不能省。如下:
0755n; // Uncaught SyntaxError: Invalid or unexpected token
0o755n; //493n
Number 对象有 8 个静态属性,分别是:
常量 | 说明 |
---|---|
Number.MAX_VALUE Number.MIN_VALUE |
能表示的最大正数 能表示的最小正数(最接近 0 的正数) |
Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER |
最大安全整数,+(253 - 1) 最小安全整数,-(253 - 1) |
Number.EPSILON |
能表示的数字之间的最小间隔 |
Number.NEGATIVE_INFINITY Number.POSITIVE_INFINITY |
正负无穷大(特殊值) 上溢时返回 |
Number.NaN |
Not a Number(特殊值) |
它们的值分别是:
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324
Number.MAX_SAFE_INTEGER; // 9007199254740991 = 2**53-1
Number.MIN_SAFE_INTEGER; // -9007199254740991
Number.EPSILON; // 2.220446049250313e-16 = 2**-52
Number.POSITIVE_INFINITY; // Infinity
Number.NEGATIVE_INFINITY; // -Infinity
Number.NaN; // NaN
那么,结合《Number 在内存中的存储方式》,如何理解它们的含义和值呢?
- 符号位,1 位,0 正 1 负
- 指数,11 位,实际范围是从 -1022 到 +1023
- 有效数, 53 位,包括 1 个默认前导位 + 52 个显式存储位
IEEE 754 binary64 (双精度浮点数)里的指数,当它是“全 0”和“全 1”的时候是为特殊数字保留的,对此已在《浮点数的指数编码》里介绍过了,这里便不再赘述,就直接用结论了。
有效位“全 0” | 有效位非“全 0” | |
---|---|---|
指数“全 0” | 表示 ±0 |
表示次正规数,此时: - 有效位的默认前导位由 1 变 0 - 指数按最小指数值来解释,即 -1022 |
指数“全 1” | 表示 ±Infinity |
表示 NaN |
最大正数 MAX_VALUE
应该是:正数即符号位为 0,11 位指数是“全 1-1”,52 位有效数是“全 1”。如下:
MAX_VALUE 的 IEEE 754 binary64 存储为:0x7FEFFFFFFFFFFFFF
真实值就是:(-1)0 * 1.11...11(2) * 22046-1023
0b11111111110; // 2046
(2**53 - 1) * 2**971; // 1.7976931348623157e+308
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MAX_VALUE === (2**53 - 1) * 2**971; // true
最小正数 MIN_VALUE
应该是:正数即符号位为 0,11 位指数是“全 0”,52 位有效数是“全 0+1”。此时即为次正规数,如下:
MIN_VALUE 的 IEEE 754 binary64 存储为:0x0000000000000001
真实值就是:(-1)0 * 0.00...01(2) * 2-1022
2**-1074; // 5e-324
Number(2**-1074).toPrecision(70); // '4.940656458412465441765687928682213723650598026143247644255856825006755e-324'
Number.MIN_VALUE; // 5e-324
Number.MIN_VALUE === 2**-1074; // true
在介绍最大安全整数和最小安全整数之前,我们先来认识下什么是“安全整数”。
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.
大意是:安全整数是形容数学概念上的一个整数在 JavaScript 里的表示方式。如果说一个整数是安全的,就意味着它能在 JavaScript 中被唯一地表示。
那么,如何理解“被唯一”地表示呢?来看个例子。
当 IEEE 754 binary64 的 53 个有效位全是 1 且都处于整数位置时,即实际指数值是 52,偏正指数值是 52+1023 = 1075 = 10000110011(2),此时的内存表示如下:
真实值就是:(-1)0 * 1.11...11(2) * 252
当它加 1 时,值会变成 253 = 1000...00(2) = 1.00...000 * 253。注意,末位的 0 之所以被删,是因为内存里的 IEEE 754 binary64 格式的有效位只有 53 个(包括默认的前导位 1),所以需要裁切精度( 0 舍 1 入)。
当再加 1 时,值会变成 253 + 1 = 1000...01(2) = 1.00...001 * 253 ≈ 1.00...01 * 253。
为了方便阅读,我们用表格的形式来描述。十进制那列是从 253 - 1 即 9007199254740991 开始的,每行的值依次加 1。如下:
十进制 | 二进制 | 二进制的科学记数法 | IEEE 754 binary64 |
---|---|---|---|
9007199254740991 即 253 - 1 |
111...11 | 1.11...11(2) * 252 | 1.11...11(2) * 252 |
9007199254740992 即 253 |
1000...00 | 1.000...0 |
1.00...00(2) * 253 |
9007199254740993 | 1000...01 | 1.000...0 |
1.00...01(2) * 253 |
9007199254740994 | 1000...10 | 1.000...1 |
1.00...01(2) * 253 |
9007199254740995 | 1000...11 | 1.000...1 |
1.00...10(2) * 253 |
9007199254740996 | 100...100 | 1.00...10 |
1.00...10(2) * 253 |
9007199254740997 | 100...101 | 1.00...10 |
1.00...11(2) * 253 |
9007199254740998 | 100...110 | 1.00...11 |
1.00...11(2) * 253 |
... |
对于精度裁切,上表用的是四舍五入(即 0 舍 1 入)的舍入方式。如果是用向 0 舍入的方式(即直接截断),那么相互重叠的数字会有一点点出入。
我们可以很直观地看到,当二进制有效数的位数大于 53 位时,就得 0 舍 1 入地舍弃最后一位,这会导致值不同的两个数字会有相同的 IEEE 754 binary64 表示。这,就是没有“被唯一”地表示。
- 当指数是 53 时,是 2 个整数共用一个内存存储,因为会舍去末位的 53-52 = 1 位
- 当指数是 54 时,是 4 个整数共用一个共存储存,因为会舍去末位的 54-52 = 2 位
- 当指数是 55 时,是 8 个整数共用一个共存储存,因为会舍去末位的 55-52 = 3 位
- 当指数是 56 时,是 16 个整数共用一个共存储存,因为会舍去末位的 56-52 = 4 位
- ...
JavaScript 里的安全整数的范围是 [-(253-1), +(253-1)],没有包含边界 ±253。虽然 253 在内存中是能被唯一表示的,但是考虑到如果是用直接截断的方式,它是会和 253+1 重叠的,所以在 JavaScript 中,一旦精度被四舍五入了,也会被视为是不安全的。如下:
Number.isSafeInteger(2**53 - 1); // true
Number.isSafeInteger(2**53); // false
Number.isSafeInteger(2**53 + 1); // false
在 JavaScript 中,说一个整数是安全的,就意味着它能唯一地表示一个数学意义上的整数,且在存储时没有被舍弃精度。
最大安全整数 MAX_SAFE_INTEGER
应该是:正数即符号位为 0,指数的实际值是 52(即内存值为 52+1023 = 1075 = 10000110011(2) ),52 位有效数是“全 1”。如下:
MAX_SAFE_INTEGER 的 IEEE 754 binary64 存储:0x433FFFFFFFFFFFFF
真实值就是:(-1)0 * 1.11...11(2) * 252
0b10000110011; // 1075
2 ** 53 - 1; // 9007199254740991
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MAX_SAFE_INTEGER === 2 ** 53 - 1; // true
最小安全整数 MIN_SAFE_INTEGER
除了符号和最大安全整数 MAX_SAFE_INTEGER
不同之外,其余都一样,即:负数即符号位为 1,指数的实际值是 52(即内存值为 52+1023 = 1075 = 10000110011(2)),52 位有效数是“全 1”。如下:
MIN_SAFE_INTEGER 的 IEEE 754 binary64 存储:0xC33FFFFFFFFFFFFF
真实值就是:(-1)1 * 1.11...11(2) * 252
0b10000110011; // 1075
2 ** 53 - 1; // 9007199254740991
Number.MIN_SAFE_INTEGER; // -9007199254740991
Number.MIN_SAFE_INTEGER === -(2 ** 53 - 1); // true
在《如何理解二进制浮点数的精度损失》里曾提到浮点数到实数的映射,如下图:
中间有覆盖不到的小断层
能表示的两个数字之间的“间隔”,就是图中的那些个“小断层”。那么,这些断层是均匀的吗?如果均匀,值是多少?如果不均匀,最小值和最大值分别是多少?
要回答这个问题,我们就得看看二进制到十进制的转换了。在《为什么 toPrecision(17) 能让内存中的数值原形毕露?》里我们是将整数部分和小数部分分开来讨论的,相关结论是:
在真实的 IEEE 754 binary64 存储中,二进制的那 53 个有效位是可以同时有整数部分和小数部分的,如果再考虑指数,这会让每个有效位上的数字权重(即2的几次方)变成流动的。所以显然,两个能表示的数之间的“间隔”是不均匀的。
那间隔的最小值和最大值分别是多少?
假设现在有 4 个二进制有效位,其中包括 1 个默认前导位和 3 个显式存储位。我们看看在这种情况下,二进制到十进制是一个什么样的对应关系。如下:
格式 | 二进制 | 十进制 | 间隔值 |
---|---|---|---|
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 = 2-3 |
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 = 2-3 |
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 = 2-2 |
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 = 2-1 |
1xxx | 1000 1001 1010 1011 1100 1101 1110 1111 |
8 9 10 11 12 13 14 15 |
1 = 20 |
同理,当我们有 53 个二进制有效位时(1 个默认前导位 + 52 个显式位),从 0 \~ 1 \~ 21 \~ 22 \~ 23 \~ 24 \~ ... \~ (253 - 1) 之间,每段都是连续的等差数列,只是差值不同而已。其中,最小差值是 2-52,最大差值是 20 = 1。
之所以只考虑了次正规数和 0 ≤ 指数实际值 ≤ 52 的情况,是因为当指数过大或者过小时,能表示的数字就不是线性连续的了。感兴趣的小伙伴们,可以自行推导下。
在数轴上,二进制和十进制的逻辑类似
综上,最小间隔 EPSILON
就是能表示的等差数列的最小差值,即 2-52 = 2.220446049250313e-16
2**-52; // 2.220446049250313e-16
Number.EPSILON; // 2.220446049250313e-16
Number.EPSILON === 2**-52; // true
这俩就是指数位“全 1”的特殊情况(之一):指数位是“全 1”,有效数位是“全 0”。如下图:
infinity 的 IEEE 754 binary64 存储:0x7FF0000000000000
-infinity 的 IEEE 754 binary64 存储:0xFFF0000000000000
Number.POSITIVE_INFINITY === Infinity; // true
Number.NEGATIVE_INFINITY === -Infinity; // true
NaN
也是指数位“全 1”的特殊情况(之一):指数位是“全 1”,只要有效数位不是“全0”的都是 NaN
(忽略符号位)。如下图:
从 0x7FF0000000000001 \~ 0x7FFFFFFFFFFFFFFF 都是 NaN
0 有两种表示方式:-0 和 +0(0 是 +0 的别名)。在实际开发中,这几乎没啥影响。但需要注意一点,就是当 0 是被除数的时候,会有不同:
33 / 0; // Infinity
33 / -0; // -Infinity
-0 === 0; // 虽然是 true
+0 的 IEEE 754 binary64 存储
-0 的 IEEE 754 binary64 存储
本节介绍了 Number 对象里的 8 个静态属性:
Number.MAX_VALUE
即最大正“正规数”Number.MIN_VALUE
即最小正“次正规数”Number.MAX_SAFE_INTEGER
Number.MIN_SAFE_INTEGER
Number.EPSILON
NaN
, ±Infinity
, ±0
Number.NaN
Number.NEGATIVE_INFINITY
Number.POSITIVE_INFINITY
数学归纳法咋推出的?
数学归纳法咋推出的?
就是依次总结下规律,来推出当 n+1 时的情况
目录
87