深入了解JS基础
JS 的 Number 类型精度问题
我们知道,Java 语言中的长整型范围为 +2^63-1 ~ -2^63-1,而 JavaScript 的 Number 基本类型采用 64 位浮点型表示,为什么 JS 中整型的最大安全范围不是 +2^63-1 ~ -2^63-1 呢?
Java的 的长整型整数也是采用 64 位表示,除了第一位是符号位,剩下的 63 位都可以表示数字。
而 JavaScript 没有单独的整型类型 (Bigint 除外,且 Bigint 不能参与与 Number 类型的运算),整型、浮点型都统一用 64 位浮点型来表示,采用的是 IEEE754 标准。
IEEE754 规定,64 位的浮点型中,第 1 位表示正负,2~12 位表示指数位 (实际存储的时候必须加上一个偏移值 1023),剩下的 64 - 1- 11 = 52 位表示整数位,因为浮点数采用科学计数法,第一位固定是 1,可以不用表示,但是运算时会加上。
比如:
0 01111111011 0000000000000000000000000000000000000000000000000000
表示的就是 1.0000000000000000000000000000000000000000000000000000 * 2^(1019 - 1023) = 0.0625
Number.Max_Value 与 Number.MAX_SAFE_INTEGER
我们打印 Number 的这两个属性:
1 | console.log(Number.MAX_VALUE); |
1. Number.Max_Value
其中 Number.Max_Value 就是 Number 类型可以表示的最大浮点数,计算方式如下:
你可以猜到会是:
0 11111111111 1111111111111111111111111111111111111111111111111111,
但这种情况在 IEEE754 标准中表示 NaN,最大的数其实是:
0 11111111110 1111111111111111111111111111111111111111111111111111
转换成二进制的科学计数法表示如下:
1.1111111111111111111111111111111111111111111111111111 * 2^(2046 - 1023)
= 1.1111111111111111111111111111111111111111111111111111 * 2^1023
= (2^53 - 1) * 2^971
我们可以在浏览器调试窗口中验证:
1 | (Math.pow(2, 53) - 1) * Math.pow(2, 971) // 1.7976931348623157e+308 |
2. Number.MAX_SAFE_INTEGER
现在就可以解释为什么 JS 的最大整数为 +2^53-1 ~ -2^53-1。
因为 IEEE754 浮点数中整数位最大表示的数为:
1111111111111111111111111111111111111111111111111111 = 2^53-1
比这还大一位的数字的表示为:
100000000000000000000000000000000000000000000000000000 = 2^53
在计算机中表示为:
0 10000110101 0000000000000000000000000000000000000000000000000000 0
注意到我们省去掉了一位,按照向偶舍入的规则,不会产生进位。所以这个数还是可以精确表示的,没有问题。
我们再来看看比 MAX_SAFE_INTEGER 大二的数:
100000000000000000000000000000000000000000000000000001
= 1.00000000000000000000000000000000000000000000000000001 * 2^53
在计算机中表示成:
0 10000110101 0000000000000000000000000000000000000000000000000000 1
注意到我们省去掉了一位,按照向偶舍入的规则,还是不会产生进位。这个时候就有问题了,这个数跟刚才那个数竟然是相等的,我们来验证下:
1 | const a = Number.MAX_SAFE_INTEGER |
所以 Number.MAX_SAFE_INTEGER 表示能够准确表示的整数。
进行大数运算时,如果涉及到的的数值超过了 Number.MAX_SAFE_INTEGER,运算就会有误差了,此时的运算最好采用 Bigint 类型。
3. Number.MIN_VALUE
Number.MIN_VALUE 表达的意思是 JavaScript 能够表示最小的正数,及很接近于 0 的数字,数值为:
1 | console.log(Number.MIN_VALUE) // 5e-324 |
0.2 + 0.1 为什么不等于 0.3
这其实是计算机表示浮点数的过程中,由于存储空间的显示,对于某一些浮点数服务精确地表示。
对于十进制转二进制,整数部分除二取余,倒序排列,小数部分乘二取整,顺序排列,所以:
0.1 转化为二进制
0.0 0011 0011 0011 0011 0011 0011 … (0011循环)
0.2 转化为二进制
0.0011 0011 0011 0011 0011 0011 0011 … (0011循环)
然后采用 IEEE754 标准表示:
0.1
指数位: -4;
整数位: 1.1001100110011001100110011001100110011001100110011010 (52位)
0.2
指数位: -3;
整数位: 1.1001100110011001100110011001100110011001100110011010 (52位)
0.1 + 0.2
0.1100110011001100110011001100110011001100110011001101 (52位) 指数位: -3
+
1.1001100110011001100110011001100110011001100110011010 (52位) 指数位: -3
=
10.0110011001100110011001100110011001100110011001100111 (52位) 指数位: -3
=
1.00110011001100110011001100110011001100110011001100111 (53位) 指数位: -2
此时整数位已经溢出了,最后一位为 1,所以进位:
1.0011001100110011001100110011001100110011001100110100 (52位) 指数位: -2
= 1.0011001100110011001100110011001100110011001100110100 * 2 ^ -2
= 0.010011001100110011001100110011001100110011001100110100
= 0.30000000000000004