-
Notifications
You must be signed in to change notification settings - Fork 1
Description
前几天遇到一个问题,用“+”将一个数字字符串转成数字后,成功转成数字,但是数值不对了。查阅一下才发现Number还有MAX_SAFE_INTEGER 和 MAX_VALUE 的区别。
MAX_VALUE: JavaScript 中可表示的最大的数。它的近似值为 1.7976931348623157 x 10308。
MAX_SAFE_INTEGER:常量表示在 JavaScript 中最大的安全整数(maxinum safe integer)。
如果一个数字不是安全整数,那么在计算时,就会差生误差。
什么是最大安全整数?
他的“安全”意思是说能够one-by-one表示的整数,双精度数表示和整数是一一对应的,反过来说,在这个范围以内,所有的整数都有一个唯一的浮点数表示,这个浮点数也唯一表示一个对应的整数,这叫做安全整数。也就是说在计算机中,某个二进制串只表示一个数。最大安全整数范围:(-2^53, 2^53)
还是不明白?还有为什么是2^53次方呢?
这与浮点数的二进制表示有关。
根据国际标准IEEE 754,任意一个二进制浮点数V可以表示成下面的形式:
v = (-1)^s * M * 2^E
- (-1)^s表示当前是正数还是负数,s为0 时,v为正数, s为1时,v为负数。
- M 表示为有效数字, 比如 1010 可以表示为 1.01*2^3 这里的1.01就是有效数字M。1 ≤ M < 2。
- 2^E表示指数位,E为2的指数值
举个栗子。十进制的5.0,写成二进制是101.0,相当于1.01×2^2。那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2。那么,s=1,M=1.01,E=2。
JavaScript中所有数字包括整数和小数都只有一种类型 — Number。它的实现遵循 IEEE 754 标准,使用64位固定长度来表示,也就是标准的 double 双精度浮点数。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
IEEE 754对于E 和 M还有一些规定:
关于M:
因为M的范围为 1 ≤ M < 2, 所以 M的个位数必定为1, 即M = 1.XXXXXX 。所以IEEE 754 规定M的第一位不需要保存。在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。
比如保存1.01时,只保存01 ,在读取的时候才加上1. 这样可以节省1位有效数字。
所以64位的浮点数虽然只有52位用来M 其实最大可以表示53位的 M。
关于E:
- 首先,E是一个无符号整数。所以双精度浮点数,E有11位,范围为 0 ~ 2^11-1,即 0 ~ 2047。但是科学计数法中E可以为负数。所以IEEE 754规定,E的真实值必须再减去一个中间数,对于11位的E,这个中间数是1023。
比如,2^10的E是10,所以保存成64位浮点数时,必须保存成10+1023 =1033,即10000001001。
2.然后,指数E还可以再分成三种情况:
(1)E不全为0或不全为1。这时,浮点数就采用上面的规则表示,即指数E的计算值减去1023,得到真实值,再将有效数字M前加上第一位的1。
(2)E全为0。这时,浮点数的指数E等于1-1023,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
(3)E全为1。这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);如果有效数字M不全为0,表示这个数不是一个数(NaN)。
回归正题,为什么Number的最大安全整数为2^53 -1 ?
回顾一下浮点数的表示:
v = (-1)^s * M * 2^E;
首先让 s=0, v = M * 2^E;
因为64位的浮点数,用52位去表示M,再加上 M 默认节省的一位, 所以M最大为 1. 111...111 总共53个1,当指数值E= 52时,得到一个最大的 数字。 2^53-1。
如果超过这个数值, 我们只能通过增大指数 E 来使 v 变大。 比如 2^53 , 则表示为 1.0000...00 * 2 ^53(M为52个0,E = 53)。
但是 2^53 + 1 同样表示为 1.0000...00* 2 ^53(M为52个0,E = 53)。 因为 1.0000...01* 2 ^53(M为51个0 ,1个1,E = 53),表示为 2^53 + 2。 1.0000...00* 2 ^53(M为52个0,E = 53) 其实表示了2个数字 。2^53(9007199254740992) 和 2^ 53 +1(9007199254740993)。所以当我们+ ' 9007199254740993 ' 时,得到的数字是9007199254740992。
还不明白?
那缩小范围 如果计算机用2位来储存 M, 即M最大为 1.11。 那么 E = 2时 我们得到最大的数 111,还想变大 则只能让 E=3,此时得到 1000 (1.00 *2^3),而此时计算机 能表示的下一个数字是 1010( 1.01 * 2^ 3), 无法表示1001, 所以 1001对应的数字在计算机内也只能表示为 1000, 这样就会造成计算误差。
所以js中Number的最大安全数字为 2^53 -1.
可以使用Number.isSafeInteger() 判断当前数字是否是安全数字。
参考文章:
阮一峰——浮点数的二进制表示