IEEE 754标准
在C/C++等编程语言中,浮点数在计算机中的表示采用的是IEEE 754标准。该标准一共的规定了四种表示浮点数值的方式,其中常见的表示方式有两种:单精确度(32位)和双精确度(64位)。这两种表示方式在C/C++语言中分别可以用float
和double
关键字来定义。
单精度浮点数的表示方法
单精度浮点数的表示方法其实就是把一个32位分成了三个部分:
- 第一个部分,占1位,表示浮点数的符号,英文Sign,简称为S。
- 第二个部分,占8位,表示浮点数的指数,英文Exponent,简称为E。
- 第三个部分,占23位,表示浮点数的尾数,英文Mantissa,简称为M。
如下图所示:
31 22 0
+----------------------------------+
|S|EEEEEEEE|MMMMMMMMMMMMMMMMMMMMMMM|
+----------------------------------+
30 23
对应浮点数表示的值为:
结合上面的公式,来理解各个部分的意义就更容易了:
符号部分,$(-1)^S$
当S为0时,$(-1)^0 = 1$,表示浮点数的值大于0;同理,当S为1时,$(-1)^1 = -1$,表示浮点数的值小于0。
指数部分,$2^{(E-127)}$
首先要明白:对于任何一个正实数x,都可以找到一个整数n,使得$2^n <= x < 2^{n+1}$。举个例子,比如5,可以找到n=2,使得$2^2 <= 5 < 2^3$,即$4 <= 5 < 8$。浮点数中的指数部分就是这里的n。
那么为什么还要减去127呢?因为当浮点数值的绝对值小于1时,指数部分其实是小于0的。举个例子,比如0.45,只有$n=-2$时,使得$2^{-2} <= 0.45 < 2^{-1}$,即$0.25 <= 0.45 < 0.5$。而浮点数中指数部分占8位,可以表示范围[0-$2^8$),即[0-256),为了在指数部分能够表示负数,
IEEE 754规定
减去127,即$n = E - 127$。所以,如果浮点数的$n=-2$,那么实际的E应该是125。尾数部分,$(1 + \frac{M}{2^{23}})$
尾数部分占23位,可以表示范围[0-$2^{23}$),即[0-8388608)。
可以这样理解尾数部分:把一条线分成8388608个段,也就是把$2^n$到$2^{n+1}$分成8388608个线段。尾数部分的值M,表示从$2^n$到浮点数值的绝对值x所要经过的线段数量,也就是$2^n$到x的长度占$2^n$到$2^{n+1}$长度的比例是多少。这个比例的值就是尾数部分公式中$\frac{M}{2^{23}}$的来由,而加1则表示线段的起点$2^n$的值。
单精度浮点数举例
有了上面的说明,再结合例子来理解一下,比如浮点数3.14:
符号部分
因为3.14大于0,所以$S = 0$。
指数部分
$2^1 <= 3.14 < 2^2$,所以$n=1$,那么$E = n + 127 = 128$。
尾数部分
$2^1$到3.14的长度占$2^1$到$2^2$长度的比例是$\frac{(3.14 - 2^1)}{(2^2 - 2^1)} = 0.57$
总线段数量为$2^{23}$,那么,从$2^1$到3.14所要经过的线段数量$M = 0.57 * 2^{23} = 4781506.56$,四舍五入,转换成整数结果为$M = 4781507$。因为有四舍五入,所以浮点数保存的数据和实际数据是有误差的。
把S、E、M转换成二进制,就可以得到3.14的二进制表示0x4048f5c3
:
S E M
+----------------------------------+
|0|10000000|10010001111010111000011|
+----------------------------------+
最后代入浮点数公式计算:
也可以使用IEEE-754浮点数转换工具来分析浮点数的表示方法。
双精度浮点数的表示方法
双精度浮点数和单精度浮点数的原理是一样的,只是各个部分长度不同而已。
双精度浮点数的表示方法其实就是把一个64位分成了三个部分:
- 第一个部分,占1位,表示浮点数的符号,英文Sign,简称为S。
- 第二个部分,占11位,表示浮点数的指数,英文Exponent,简称为E。
- 第三个部分,占52位,表示浮点数的尾数,英文Mantissa,简称为M。
对应浮点数表示的值为: