之前被大佬的一段代码所诱导,所以我花了不少时间恶补了js的number类型的东西。
首先介绍一下js的number类型:number遵循的是IEEE 754规范中的双精度储存,而且是64位,如下图:
对于js,所有数字都是按64位形式存入,不分浮点型和整型,所以在js中,1===1.0
对于js的number,按上图来说:
- 第0位表示符号位,s,正数的话是0,负数的话是1
- 第1位到第11位表示指数部分,e
- 第12位到第63位表示尾数部分,f
符号位决定了一个数的正负,指数部分决定了数值的大小,尾数部分决定了数值的精度。 IEEE 754规定,有效数字第一位默认总是1,不保存在64位浮点数之中。也就是说,有效数字总是1.xx…xx的形式,其中xx..xx的部分保存在64位浮点数之中,最长可能为52位。因此,JavaScript提供的有效数字最长为53个二进制位(64位浮点的后52位+有效数字第一位的1)。
所以js中的number最大能表示的数是2的53次方:9007 1992 5474 0992
超过了这个数就会产生不精确的现象。
说细点的话,900719254740992是可以表示的最大数,但是不安全。这就涉及到了安全的整数这个概念了。
“安全”意思是说能够one-by-one表示的整数,也就是说在(-2^53, 2^53)范围内,双精度数表示和整数是一对一的,反过来说,在这个范围以内,所有的整数都有唯一的浮点数表示,这叫做安全整数。给个例子:
在js中:
- 2^53是这么存的:符号位:0,指数:53,尾数:1.00000...000(一共52个0)
- 而2^53-1是这么存的:符号位:0,指数:52,尾数:1.11111...111(小数点后52个1)
- 2^53-2的存法:符号位:0,指数:52,尾数:1.11111...110(小数点后51个1,一个0)
这样看的出,只要按对应的数据修改尾数和指数就能得到对应的存法,每一个数据都对应唯一的存法。
但是对于2^53+1的存法:符号位:0,指数:53,尾数:1.00000...000(一共52个0)
2^53+1的存储和2^53一样。这样就不再“一一对应”。为什么两个数会存的一样?因为在指数位是53的情况下,尾数在乘以2的53次幂后,相当于小数点向后移动了53位。此时,小数点不是在尾数最后一位的后面,而是在其后面的后面。所以后面的数字就不安全了。
至于为什么2^53不是一个安全的整数,是因为它在储存中已经丢失了一位精度,尾数部分本只能存52位,所以第53位就丢失了精度。
所以说,在js中的number运算中,不超过2^53就没有问题。若是超过的话就需要用到其他的方法了:一般有使用字符串,或者使用数组分装。
坑点:
在JS中无论是整数还是小数都是按照64位的浮点数形式存储,而整数运算会自动转化为32位有符号整数。有符号整数使用 31 位表示整数的数值,用第 32 位表示整数的符号,0 表示正数,1 表示负数,所以数值范围为 [-2^31 , 2^31-1]。
但是进行位操作时,采用32位有符号整型,这意味着其转换的结果也是32位有符号整型。
对于数值不在[-2^31 , 2^31-1]这个范围的数字不操作的情况下,是无符号整型,但是一旦进行了位运算,这个数字就转化成了32位有符号整型。
比如var a=2**31
这个时候的a是无符号整型。
一旦进行位运算后,类似**a=a|0
(与0进行或运算),那么a就成了有符号整型了。**
这就导致了(a|0===a)的返回值为false。也就是我之前一个帖子的疑惑了。
我试了一下,不仅类型,连值也不等了,(a|0==a)的返回值也是false。
应该是64位按32位运算的话,高位被舍弃了,所以导致值的改变。
对于超过范围的位运算,有个链接可以看看:blog.csdn.net/LingXi__Y/a…