这篇笔记主要是在看了掘金上 全栈然叔 的“365 天打卡记录”整理而来的,发现就算是 js 这种和 byte 打交道比较少的语言,在处理数字上也会有一些注意点。
Number 的存储形式
JavaScript 内部,所有数字都是以 64 位浮点数形式储存,即使整数也是如此。所以,1 与 1.0 是相同的,是同一个数。
浮点数的结构
根据国际标准 IEEE 754,JavaScript 浮点数包含 64 个二进制位。
IEEE 754 Double Floating Point Format by Codekaizen, CC BY-SA 4.0, via Wikimedia Commons
- 第一部分(蓝色):用来存储符号位(sign),第 1 位:符号位,0 表示正数,1 表示负数
- 第二部分(绿色):用来存储指数(exponent),第 2 位到第 12 位(共 11 位),指数部分
- 第三部分(红色):用来存储小数(fraction),第 13 位到第 64 位(共 52 位),小数部分(即有效数字)
fraction 决定了整数安全表示范围,也就是
(-1)^s * f * 2^e
- 符号部分 -1 or 1
- f 的范围为
1<=f<2
,使用 52 位表示 - 指数有正有负,指数位长度为 11 比特,所以能表示的数字范围为 0~2047
所以最大位应该由 f 决定,也就是说所有的 52 位用作表示整数部分。
52 位为什么可以表示 53 位小数
因为小数部分只需要表示尾数就可以,整数部分可定等于一。52 位太多不好理解,假设我们以 3 位(bit)数。
b0.10
可以表示为 1.00 * 2^-1
b0.01
可以表示为 1.00 * 2^-2
这样的话由于整数部分一定等于 1,所以可以把整数部分省略,也就是说 3 位数可以表示做小数表示的时候可以表示 4 位小数。
整数的表示范围
小数的计算
小数使用乘二取整法,我们用 0.75 举例,所有整数部分连接起来正好是 0.75 的二进制部分。
0.75 * 2 = 1.5 取整 1 剩下 0.5
0.5 * 2 = 1 取整 1 剩下 0 运算结束
而 0.1 和 0.2 情况有所不同
0.1 * 2 = 0.2 取整 0 剩下 0.2
0.2 * 2 = 0.4 取整 0 剩下 0.4
0.4 * 2 = 0.8 取整 0 剩下 0.8
0.8 * 2 = 1.6 取整 1 剩下 0.6
0.6 * 2 = 1.2 取整 1 剩下 0.2
0.2 * 2 = 0.4 取整 0 剩下 0.4
0.4 * 2 = 0.8 取整 0 剩下 0.8
0.8 * 2 = 1.6 取整 1 剩下 0.6
0.6 * 2 = 1.2 取整 1 剩下 0.2
...
所以在有限精度内是无法取到五分之一和十分之一的,所以有限的 52 个 bit 是无法表示 0.1 这种数字的唯一的方法就是截取。
判断 0.1、0.2 类似的数相等
随着使用二进制位数的增加精度会越来越高,但是譬如五分之一、十分之一是永远无法表示的。可以通过判断是否小于机器精度来判断是否相等,机器精度为 Number.EPSILON = 2^-52
References: