IEEE 754 Double Floating Point Format详解

479 阅读3分钟

存储结构

按照从左至右的顺序:第1位是符号位(sign),接下来的11位是指数位(exponent),剩余的52位是尾数位(fraction)。

IEEE754double-precision-representation-in-memory.png

规格化

exponent > 0:

(1)sign(1.b51b50...b0)2×2e1023(-1)^{sign}(1.b_{51}b_{50}...b_0)_2 \times 2^{e-1023}

非规格化

exponent = 0:

(1)sign(0.b51b50...b0)2×21022(-1)^{sign}(0.b_{51}b_{50}...b_0)_2 \times 2^{-1022}

特殊值

exponent全为1,fraction = 0,表示无穷大(符号位有效表示正负无穷)
exponent全为1,fraction > 0,表示NaN

范围

MAX_VALUE

01111111111011111111...1(52bits)0\quad11111111110\quad11111111...1(52bits)

代入公式:

(1)0×1.111...1(52bits)×221121023(-1)^0 \times 1.111...1(52bits) \times 2^{2^{11} - 2 - 1023}

化简得:

(2531)×2971(2^{53} - 1) \times 2^{971}

验证:Number.MAX_VALUE === (Math.pow(2, 53) - 1) * Math.pow(2, 971)

MIN_VALUE

0000000000000000...1(52bits)0\quad00000000000\quad0000...1(52 bits)

代入公式:

(1)0×0.0000...1(52bits)×21022(-1)^0 \times 0.0000...1(52\,bits) \times 2^{-1022}

化简得:

210742^{-1074}

验证:Number.MIN_VALUE === Math.pow(2, -1074)

MAX_SAFE_INTEGER

·表示的数是正数
·fraction全是1
·exponent的结果恰好是52,即e为1075
0100001101001111...1(52bits)0\quad10000110100\quad1111...1(52 bits)

代入公式:

(1)0×1.1111...1(52bits)×210751023(-1)^0 \times 1.1111...1(52\,bits) \times 2^{1075-1023}

化简得:

25312^{53}-1

验证: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,即(2112)÷2=1023(2^{11} - 2) \div 2 = 1023

反码

正数的反码是它本身,负数的反码是除符号位外剩余位取反。

计算机的加减法都是加法运算(减法看作加负数),符号位不可避免的参与其中,这会导致错误,因此有了反码。

例如计算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,因此有符号整数的范围是首尾相连的,可以想象成圆形,一个数的相反数总是关于圆心中心对称,他们之间的差距自然是表示范围的一半。

signed int.png

为何0.1 + 0.2 !== 0.3

从IEEE 754的表示方法中不难看出,浮点数的小数部分是由多个2n(nZ)2^{-n}(n\in Z)这样的数相加拼凑而来的,显然只有少数小数可以被精确表示(0.5、0.25、0.125……),而0.1、0.2、0.3都是近似值,相加自然不会相等。

什么时候小数相加相等

对于能够被精确表示的小数,相加之后自然也是精确的===当然会返回true。

对于不能被精确表示的小数x,x + x === 2 * x为true,其本质是fraction相同,exponent不同,因此可以推广成:任意fraction相同的小数相加,只要结果fraction仍然相同,===就会返回true。

参考

github.com/bartaz/ieee…

en.wikipedia.org/wiki/Double…