JavaScript中数字的相关
这部分介绍了JavaScript数值相关的一些比较底层的知识和概念,阅读之前,要对IEEE754的数值表示有个大概的理解。本文是个人积累的一点关于数值方面存在的问题,以及对问题原因的解释。
1.MAX_VALUE与Infinity
为什么Number.MAX_VALUE + 1 !=Infinity ?
首先我们要看看在IEEE种二者分别是怎么表示的。
在IEEE中,11位指数位的全部置1,尾数52位全部置0,表示Infinity。具体IEEE754的格式如下图
而11位指数位的前10位都置1,最后一位置0,并且所有尾数置1来表示最大值,即MAX_VALUE。如下图:
这就是JavaScript 中的 Number.MAX_VALUE: 1.7976931348623157e+308。
在IEEE754的 4.1节中对Infinity 有这样的描述:
However, an infinitely precise result with magnitude at least
(2Emax)∗(2−21−p2) ( 2 E m a x ) ∗ ( 2 − 2 1 − p 2 )shall round to INFINITY with no change in sign;
这段话的意思是指,只有大于上面这个式子所代表的值时,才会得到Infinity。所以根据IEEE754对双精度浮点数的规定,上面的式子可以表示为:
所以,实际Number.MAX_VALUE距离Infinity的差距就是上面这个式子的值与Number.MAX_VALUE的差,这个差值大概为
2. 最大的安全整数值?
1.为什么最大的安全整数是(2^53-1)?
我们知道,JavaScript中最大的安全整数是(2^53-1) 。安全意味着不超过这个数的每个整数都有一个唯一的浮点数表示,即一对一的。
比如为什么2^53不安全,虽然它表示正确,但实际上,已经丢失了精度。
可以看看下面console打印出来的结果
alert(9007199254740992 == 9007199254740993);//会alert出true来
即 Math.pow(2,53)+ 1 = Math.pow(2,53);//会alert出true来
2.为什么是2^53而不是2^52
因为IEEE754规定尾数第一位隐含为1,是不写的。所以这样就多了一位,可以最多表示为53位。
3. 相等吗?
0.1 + 0.2 !=0.3 //true
//实际在控制台:
0.1 + 0.2 = 0.30000000000000004。
//另外的例子
0.1 + 1 - 1 != 0 //false
上面的小数部分0.1和0.2都不能精确地表示为二进制浮点数。
这是因为小数不能在52位的尾数中转换成有限的二进制表示,实际上多少位都无法完全表示,就像我们知道的0.3333….的无限循环,所以造成精度丢失。在这里,0.1这个十进制小数在计算机中被转换为为二进制进行运算的时候,转换出来的二进制数是无限循环的,而双精度的尾数位是根本存不下这么多数字,所以会产生舍入,这个舍入导致了精度的丢失。在0.1~0.9的这9个小数中,只有0.5能够精确表示。
怎么保证这样的运算安全。实际上,有小数输入时候,不应该直接比较它们,而是允许它们有一定的误差上限,这个误差上限称为机器精度(epsilon)。对于JavaScript,双精度的标准机器精度值是2^ -53
var epsEqu = function () { // IIFE, keeps EPSILON private
var EPSILON = Math.pow(2, -53);
return function epsEqu(x, y) {
return Math.abs(x - y) < EPSILON;
};
}();
在ES6中,目前也已经提供了这个数值:Number.EPSILON。
4. 存在的转换
数字的表示 当整数的位数超过21的时候,会被转换成科学记数法 对于小数,当小数是以0.开始,并且紧后面是连续超过5个0.会被转换成科学记数法
>1234567890123456789012
1.2345678901234568e+21
>123456789012345678901
123456789012345680000
>0.0000003
3e-7
>0.000003
0.000003