JS采用IEEE 754标准定义64位浮点格式表示数字。所有的数值都由浮点数表示,比如3其实是3.0。
其次,需要了解下浮点数一般的保存格式是什么。拿一个十进制的浮点数 abcd.efg 作为例子,其中的字符都是0~9的数字。如果采用浮点数保存的方式,则会将abcd.efg表示成下面的方式
abcd.efg = -1^(0)*[a(10^3) + b(10^2) + c(10^1) + d(10^0) + e(10^-1) + f(10^-2) + g(10^-3)]
abcd.efg = -1^(0)*[a.bcdefg(10^3)]
我们做进一步扩展,如果是一个二进制的浮点数 abcd.efg,其中的字符都是0或者1(a是例外,a作为最高位只能为1,不然就可以将其表示为bcd.efg的格式)。那么它的表达格式,可以转换成下面的格式:
abcd.efg = -1^(0)*[a(2^3) + b(2^2) + c(2^1) + d(2^0) + e(2^-1) + f(2^-2) + g(2^-3)]
abcd.efg = -1^(0)*[a.bcdefg(2^3)]
将二进制和十进制进行比较,我们会发现,两者只有幂计算的基数不一样,各自为自己的进制数,而其他的结构类似。我们对其统一归纳,其中的a.bcdefg称为尾数/定点数,3称为幂指数,而最开始的0则称为符号位。而IEEE754协议就是按照上面的格式对浮点数进行定义的。
IEEE754协议64位浮点数数据结构
根据协议,IEEE754的64位浮点格式数据结构如下:
整个64位比特,被分为了三部分
- 符号位:1bit,符号位决定正负数,等于1表示为负数,等于0表示为正数
- 指数位:11bit,指数位决定了数值表示的幂指数,也就是上面中的3,
- 尾数位:52bit,尾数位决定了和幂指数相乘的定点数,也就是上面的a.bcdefg
1:符号位
符号位是最容易理解的,它表示了当前数值的符号,通过(-1)作为基数,幂上符号位的值得出。当符号位为1的时候,(-1)^1 = -1;当符号位为0的时候,(-1)^0 = 1
2:指数位
指数位占用了11个bit,可以表示0~2047。在实际使用中,为了表示负的幂指数(在表示大于0,但非常小的小数时需要,比如0.00000012 = 1.2(10^-7))。在协议中定义了一个偏移量off。真正的幂指数=指数位-off。为了均衡,off一般取指数位的中位数,即(2047-1)/2 = 1023。通过这样的处理,指数位可以表示的幂指数范围为 -1023 ~ 1024。其中有几个特殊的幂指数有特殊的含义,我们在下面会做进一步解释。
3:尾数位
尾数位表示我们上面例子里面的a.bcdefg,且a不为0。这个数值存放在剩下的52bit里面。计算机作为一个二进制的系统,a在不为零的时候,只能为1。为了可以表示更多的数,协议中将a进行了省略,也就是说,在52位尾数位中只存储了bcdefg,而将a自动省略掉了。这也是为什么叫做尾数位(fraction)的原因所在。
总结下来,在IEEE754标准协议中,一个浮点数的表示可以写作
float = -1^(s)*1.f * 2^e // s为符号位,f为尾数位,e为(指数位-1023)
这种方式,也被称为规格化(normalized).
根据指数位的不同,JS中的数值分为了几段
e =0 & f=0 表示区间
[-0, +0]
e = 0 & f != 0 表示区间
[0.00……01 * 2^(-1022), 0.11……11 * 2^(-1022)],最小累加精度为 0.00……01 * 2^(-1022)
e = [1, 2046] 表示区间
[1.0 * 2^(-1023) ~ 1.11……11 * 2^(1023)],最小累加精度为 0.00……01 * 2^(e-1023)
这里有个需要注意的地方,即整数的精度问题。按照最小累加精度的定义,当(e-1023) = 52的时候,最小精度0.00……01 * 2^52 = 1。这个时候,1.11……1 * 2^52 是该是可以准确表达整数的区间最大值。当其再加上1的时候,变为1.0 * 2^53,此时的最小精度变为了0.00……01 * 2^53 = 2。已经无法精确表示整数。因此JS的可以精确表示的最大整数就是 1.0 * 2^53。
e = 2047 & f = 0 表示区间
此时表示为 无穷大(infinity0)
e = 2047 & f != 0 表示区间
此时表示为NaN