存储结构
按照从左至右的顺序:第1位是符号位(sign),接下来的11位是指数位(exponent),剩余的52位是尾数位(fraction)。
规格化
exponent > 0:
非规格化
exponent = 0:
特殊值
exponent全为1,fraction = 0,表示无穷大(符号位有效表示正负无穷)
exponent全为1,fraction > 0,表示NaN
范围
MAX_VALUE
代入公式:
化简得:
验证:Number.MAX_VALUE === (Math.pow(2, 53) - 1) * Math.pow(2, 971)
MIN_VALUE
代入公式:
化简得:
验证:Number.MIN_VALUE === Math.pow(2, -1074)
MAX_SAFE_INTEGER
·表示的数是正数
·fraction全是1
·exponent的结果恰好是52,即e为1075
代入公式:
化简得:
验证:Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1
MIN_SAFE_INTEGER
MIN_SAFE_INTEGER是MAX_SAFE_INTEGER的相反数,只需将符号位改成1即可。
验证:Number.MIN_SAFE_INTEGER === 1 - Math.pow(2, 53)
为何exponent要用移码表示,且偏移量为1023
exponent的符号位会导致几个问题:
exponent存在正负0
exponent预留了全为1和全为0的值处理特殊情况,分别对应了上下边界,如果不用移码会破坏这样的线性表示
阶码比较需要额外的比较器
移码将exponent映射成无符号整数,上述三个问题迎刃而解。
偏移量为数值表示范围除以2,即。
反码
正数的反码是它本身,负数的反码是除符号位外剩余位取反。
计算机的加减法都是加法运算(减法看作加负数),符号位不可避免的参与其中,这会导致错误,因此有了反码。
例如计算3 - 5 = -2,
0000 0011 + 1000 0101(原码)
0000 0011 + 1111 1010 = 1111 1101(反码)
1000 0010(将结果转化回原码)
why?
不难想到:负数的原码 + 负数的反码 = 1111 1111(多少位就有多少个1,本例是8个)。
也就是说反码加了多少原码就会相应减去多少,例如1111 1010(-5的反码)加3,1000 0101(-5的原码)自然减少3,这里要思考二进制,否则会误以为-5 - 3 = -8,实际上是 5 - 3 = 2,再考虑符号位变成-2。
补码
原码的反码+1。
反码运算时会出现-0,因此向前进一位。
移码
移码是原码的补码符号位取反。
对于计算机的底层操作而言,它不会使用-1023这种操作,况且1023只是exponent为11位时的特殊值,而符号位取反总能让这个数变成减去偏置(当前位数能表达数值范围的一半)后的模样。
因为一个正数上溢之后是-0,而一个负数下溢之后是+0,因此有符号整数的范围是首尾相连的,可以想象成圆形,一个数的相反数总是关于圆心中心对称,他们之间的差距自然是表示范围的一半。
为何0.1 + 0.2 !== 0.3
从IEEE 754的表示方法中不难看出,浮点数的小数部分是由多个这样的数相加拼凑而来的,显然只有少数小数可以被精确表示(0.5、0.25、0.125……),而0.1、0.2、0.3都是近似值,相加自然不会相等。
什么时候小数相加相等
对于能够被精确表示的小数,相加之后自然也是精确的===当然会返回true。
对于不能被精确表示的小数x,x + x === 2 * x为true,其本质是fraction相同,exponent不同,因此可以推广成:任意fraction相同的小数相加,只要结果fraction仍然相同,===就会返回true。