背景:
JS中小数运算和整数运算有时候都不精确,例如经典的例子:
为什么0.1+0.2 !== 0.3,
9007199254740991(最大安全数字,文章末尾有说明)+8===9007199254741000
或者将8改成10计算结果也是这个数,这就是因为计算机是以二进制存储数据所以JS的数字不是以十进制而是以二进制且是以双精度浮点法(IEEE 754)存放,
十进制的小数,转换为二进制后,可能是无限小数(十进制数 0.3 -> 0.010011001100110011001.....),
但是计算机对数字的存储能力有限,二进制小数位过多的数或过大的整数都无法精确表示,因此会丢失一些数据,在上述情况下就会造成精度缺失,具体如下:
数字存储规则:
(在计算机中,位bit是最小的存储单位,简称为bit ,1 byte = 8 bit ,1 KB = 1024 byte ,1 MB = 1024 KB ,1 GB = 1024 MB。)
JS在计算机中,给每个数字开辟一块内存空间,尺寸固定为64位;
64位分为这三段:[1] [2~12] [13~64]
[第1段] [第2段] [第3段]
第1段:1位,表示符号位,如果为1,是负数,如果为0,是正数
第2段:11位,表示指数位,这里的指数是2为底的指数,而不是10
第3段:52位,表示有效数字
举例:
0 1000 0000 011 1111 0000 0000 0000.... 相当于: 1.1111∗2^(1027−1023)
中间段为指数位(1000 0000 011)等于1027,指数要减去1023的规则是为了能形成负指数来表示出小数;
特殊情况:
基于这种数字存放方式,有几个特殊情况的规则是:
0
指数为0,尾数为0,表示数字 0
正无穷
符号为0,指数为2047,尾数为0,表示正无穷
Infinity: 0 11111111111 00000000000...
负无穷
符号为1,指数为2047,尾数为0,表示负无穷
-Infinity: 1 11111111111 00000000000...
NaN
指数为2047,尾数不为0,表示NaN
NaN: 1 11111111111 01001010000...
最大数字
所以一个正常的数字,指数部分最多只能是2046,2047是特殊情况,因此这种方式能表示的最大数字为:
0 11111111110 1111111111.........(52个1) 相当于: 1.1111111111...∗2^1023
最大安全数字
但是这个最大数字不是安全数字,因为它前面的整数是不能通过这种方式连续表示出来的(安全数字定义:从1开始到该数字,均是连续的整数,并且该数字的下一个整数是存在的),那么这种方式能表示的最大安全数字是:
0 xxxxx 1111111111.... 指数位先待定稍后再推算,有效数字位写满到最大也就是52个1 相当于: 1.1111111111...∗2^?
十进制逢十进一,二进制逢二进一,十进制9.999...(小数点后n个9)去掉小数点的方式是*10^n,二进制1.111...(小数点后n个1)去掉小数点的方式是*2^n;
于是只要把1.1111111111...(小数点后52个1)的小数点去掉(*2^52)就是最大安全数字2^53−1=9007199254740991
于是文章开始的精度缺失例子:9007199254740991+8===9007199254741000
从数学上是计算错误,但因为正确计算结果9007199254740999无法用JS的存储数字方式表示出来,所以JS的计算结果会是9007199254741000。