为什么js中的数字很奇怪声明:读者需要对二进制有一定的了解对于JavaScript开发者,或多或少遇到过js处理数字的奇怪现象例如:>0.1+0.20.300000000000004>0.1+1-10.100000000000009>0.1*0.20.020000000000000004>MATH.POW(2,53)900719925474092>MATH.POW(2,53)出现这些奇怪的行为之前,您必须首先了解JavaScript是如何对数字进行编码的。一、JavaScript如何对数字进行编码JavaScript中的数字,无论是整数、小数、分数、正数还是负数,都是浮点数,以8个字节(64位)存储。一个数(如12、0.12、-999)在内存中占用8个字节(64位),存储方式如下:0-51:小数部分(52位)52-62:指数部分(11位)63:符号bit(1位:0表示数为正数,1表示数为负数)符号位很好理解,用来表示是正数还是负数,只有1位,两种情况(0表示正数,1表示负数)。另外两部分是小数部分和指数部分,用于计算一个数的绝对值。1.1绝对值计算公式1:abs=1.f*2^(e-1023)003:abs=0e=0,f=04:abs=NaNe=2047,f>05:abs=∞(infinity,infinity)e=2047,f=0解释:这个公式是一个二进制算法公式,结果用abs表示,而分数部分用f表示,指数部分用e表示。2^(e-1023)表示2的e-1023次方。由于小数部分占52位,f的取值范围为00...00(中间省略48个0)到11...11(中间省略了48个1)因为指数部分占11位,所以e的取值范围是0(00000000000)到2047(11111111111)从上式我们可以看出:1的存储方式:1.00*2^(1023-1023)(f=0000...,e=1023,...代表48个0)2存储方式:1.00*2^(1024-1023)(f=0000...,e=1024,...表示480)9存储方式:1.001*2^(1026-1023)(f=0010...,e=1026,...表示480)0.5存储方式:1.00*2^(1022-1023)(f=0000...,e=1022,...表示48个0s)0.625的存储方式:1.01*2^(1022-1023)(f=0100...,e=1022,...代表48个0)1.2绝对值的取值范围和边界由上式可知:1.2.100时e=0,f>0,取值范围为:f=00...01,e=0(中间省略了48个0)到f=11...11,e=0(中间省略了48个1),即:Math.pow(2,-1074)to~=Math.pow(2,-1022)(~=表示约等于),Math.pow(2,-1074)是Number.MIN_VALUE的值,js能表示的最小值(绝对值)。1.2.3e=0,f=0这只表示一个值为0,但是加上符号位,所以有+0和-0。但在运行中:>+0===-0true1.2.4e=2047,f>0这只代表一个值NaN。但是在运算中:>NaN==NaNfalse>NaN===NaNfalse1.2.5e=2047,f=0这只表示一个值∞(无穷大,无穷大)。运算中:>Infinity===Infinitytrue>-Infinity===-Infinitytrue1.3绝对值的最大安全值由上可知,8个字节最大可存储的值是Number的值.MAX_VALUE,即~=Math.pow(2,1024)-1。但是这个值并不安全:从1到Number.MAX_VALUE的数字不是连续的,而是离散的。比如:Number.MAX_VALUE-1,Number.MAX_VALUE-2等值无法通过公式得到,所以无法存储。于是来了最大安全值Number.MAX_SAFE_INTEGER,即从1到Number.MAX_SAFE_INTEGER的数字是连续的,在这个范围内的数值计算是安全的。当f=11...11,e=1075(中间省略了48个1),得到这个值111...11(中间省略了48个1),即Math.pow(2,53)-1.大于Number.MAX_SAFE_INTEGER:Math.pow(2,53)-1的值是离散的。例如:Math.pow(2,53)+1,Math.pow(2,53)+3不能通过公式求得,也不能存入内存。所以才会出现文章开头的现象:>Math.pow(2,53)9007199254740992>Math.pow(2,53)+19007199254740992>Math.pow(2,53)+39007199254740996因为Math.pow(2,53)+1无法通过公式求得,所以无法存入内存,所以只存入与这个数最接近的另一个可以通过公式求得的数,Math.pow(2,53)在内存中,是Distortion,即不安全。1.4小数的存储方法和计算,除满足m/(2^n)(m、n均为整数)的小数可以用完全二进制表示外,其他不能用完全二进制表示只能用二进制小数表示被无限逼近。(Note:[2]meansbinary,^meansNpower)0.5=1/2=[2]0.10.875=7/8=1/2+1/4+1/8=[2]0.111#0.3Approximationof0.25([2]0.01)<0.3<0.5([2]0.10)0.296875([2]0.0100110)<0.3<0.3046875([2]0.0100111)0.2998046875([2]0.010011006110)<0.5[9207(9207)]0.01001100111)...根据公式计算,直到把分数部分的52位填满,然后取最靠近的数0.3的存储方式:[2]0.010011001100110011001100110011001100110011001100110011(f=0011001100110011001100110011001100110011001100110011,e=1021)从上面可以看出,Mostofthedecimalsareonlyapproximatevalues,andonlyasmallpartarerealvalues,soonlythesefewvalues??(decimalssatisfyingm/(2^n))canbedirectlycompared,andtheotherscannotbedirectlycompared.>0.5+0.125===0.625true>0.1+0.2===0.3falseInordertocomparetwodecimalssafely,introduceNumber.EPSILON[Math.pow(2,-52)]tocomparefloatingpointnumbers.>Math.abs(0.1+0.2-0.3)0.0100110011001100110011001100110011001100110011001100110.300000000000000000.3>0.0100110011001100110011001100110011001100110011001100100.29999999999999993>0.0100110011001100110011001100110011001100110011001101000.30000000000000004>0.00000101000111101011100001010001111010111000010100011111000.0200000000000000042.Number对象中的常量2.1Number.EPSILON表示1与Number可表示的大于1的最小的浮点数之间的差值。Math.pow(2,-52)isusedforsafecomparisonbetweenfloatingpointnumbers.2.2Number.MAX_SAFE_INTEGERThemaximumsafevalueoftheabsolutevalue.Math.pow(2,53)-12.3Number.MAX_VALUEjsThemaximumvaluethatcanberepresented(themaximumvaluethatcanbestoredin8bytes).~=Math.pow(2,1024)-12.4Number.MIN_SAFE_INTEGERMinimumsafevalue(includingsign).-(Math.pow(2,53)-1)2.5Number.MIN_VALUEjsTheminimumvalue(absolutevalue)thatcanberepresented.Math.pow(2,-1074)2.6Number.NEGATIVE_INFINITYNegativeinfinity.-Infinity2.7Number.POSITIVE_INFINITYispositiveinfinity.+Infinity2.8Number.NaNnotanumber.3.Findingthereasonforthestrangephenomenon3.1Why0.1+0.2resultsin0.30000000000000004issimilartotheapproximationalgorithmfor0.3.0.1的存储方式:[2]0.00011001100110011001100110011001100110011001100110011010(f=1001100110011001100110011001100110011001100110011010,e=1019)0.2的存储方式:[2]0.0011001100110011001100110011001100110011001100110011010(f=1001100110011001100110011001100110011001100110011010,e=1020)0.1+0.2:0.0100110011001100110011001100110011001100110011001100111(f=00110011001100110011001100110011001100110011001100111,e=1021)但f=00110011001100110011001100110011001100110011001100111有53位,超过了正常的52位,无法存储,所以取最近的数:0.1+0.2:0.010011001100110011001100110011001100110011001100110100(f=0011001100110011001100110011001100110011001100110100,e=1021)js读取这个数字为0.300000000000000043.2为什么Math.pow(2,53)+1TheresultisMath.pow(2,53)becauseMath.pow(2,53)+1cannotbeobtainedbyformulaandcannotbestoredinmemory,soonlytheclosesttothisnumber,othernumbersthatcanbederivedusingformulas.最接近的数字小于此数字:Math.pow(2,53)(f=00000000000000000000000000000000000000000000000000000000000,E=1076)最接近此数字的数字:Math.pow(2,53)+21076)取第一个数:Math.pow(2,53)。所以:>Math.pow(2,53)+1===Math.pow(2,53)true参考文章HownumbersareencodedinJavaScript更多关于JavaScript如何编码数字的后续博文见https://github。com/senntyou/blogs作者:沈玉之(@senntyou)版权声明:免费转载-非商业-非衍生-保留署名(CreativeCommons3.0许可)