标签:mes 浮点数 符号 double 就是 无符号 内存 sig class
写在前面:本文要求对浮点的编码表示已有一定基础,如果对「IEEE 754」这一个词组不熟悉,请勿继续阅读,以免浪费时间。
重温 ICS,又学习了一遍计算机中的浮点数的编码表示,似乎又有了一些新的理解。
先来复习一下基本知识:对于一个浮点数,计算机采用「科学计数法」去表示:
\[
value = (-1)^S \cdot M \cdot 2^{E}
\]
\(S\) 表示这个数的符号。其中 \(M\) 总是为 1.xxxxx
这种形式( x
代表 0/1
),\(E\) 是指数。
好了,下面正式「复习」IEEE 754 浮点数标准。
在 IEEE 754 标准当中,float
采用 32 bit 去保存,double
采用 64 bit 保存,格式如下:
float
---------------------------
| S | exp | frac |
| 1 | 8 | 23 |
---------------------------
double
----------------------------
| S | exp | frac |
| 1 | 11 | 52 |
----------------------------
exp
为指数,frac
为尾数,S
为符号位。其数值表示:\(value = 1.frac \times 2^{E} \times(-1)^{S}\) 。其中,\(E\) 与 exp
相关,二者的值并不相等(什么关系可以看下面)。
这里为什么是 1.frac
呢?因为对于任意的二进制小数表示,总是可以化为 \(1.frac \times 2^{E}\) 的形式。例如,\(10.01 = 1.001 \times 2^1\) ,\(0.000101 = 1.01 \times 2^{-4}\) 。所以只需要记录 小数点后的尾数 即可(节省了 1 bit ,这也体现了计算机系统的设计哲学:尽最大努力进行优化)。
对于 \(1.0101 \times 2^{-4}\) ,易知 frac = 0101
,需要注意的是,在内存中,frac
的域从高位到低位,分别是 0101 0000 0000 0000 0000 000
。不足 23 位,后面的所有 bit 设置为 0 。
值得重新「复习」的是 exp
这一部分。
对于 exp
来说,任何时候我们都把解释为无符号数,并且保留 2 种特殊情况:
exp == 0x00
:表示非规格化浮点数 (Denormalized Number) 。exp == 0xff
:表示 2 种特殊值(后面会进一步陈述)。8 bit 的无符号数,去除最大的全 0 ,和最小的全 1 ,其取值范围为 \([1,254]\) 。显然,这无法表示 \(1.01 \times 2^{-4}\) 这种负指数的情况。因此,在 \([1,254]\) 区间内,需要对这些数值平均分配,做一次映射,一半表示负指数,一半表示正指数,因此有:
\[
E = exp - 127
\]
为什么是 127 呢?因为 \([1,254]\) 有 254 个数,127 是 254 的一半,刚好一半表示负指数,一半表示正指数。因此 \(E\) 的取值范围为:
\[
E \in [-126, 127]
\]
127 就是所谓的偏置常数 \(bias\) ,其数值由下面公式计算所得:
\[
bias = 2^{k-1} - 1
\]
其中 \(k\) 是 exp
的比特位数。因此 float
的 \(bias\) 是 127 ,double
的 \(bias\) 是 1023 。
综上所述,对于一个规格化的比特串,应采用下面的公式转换为二进制表示的小数:
\[
value = 1.frac \times 2^{exp-bias} \times (-1)^{S}
\]
Aside
为什么不把exp
看作是有符号数?这样采取补码的方式去记录,不就能直接表示正负指数?这是为了方便在硬件层面比较
float
和double
。对于正浮点数a
和b
,就可以从高位到低位进行比较,规则与unsigned int
一模一样,如果存在bit(a,i) > bit(b,i), i=31...0
,那么就说明 \(a>b\) 。如果将
exp
采用补码的方式,那么在比较 2 个浮点数时,除了要比较float
的符号位,还需要对exp
的符号位进行比较。
考虑到比特串 0x00000000
,可以得到 s = 0, exp = 0x00, frac = 0...0
,如果不考虑非规格化数的特殊规则 ,即不保留 exp = 0x00
这种特殊情况,\(E = exp - 127 = -127\) ,对应的数值应当为 \(1.0 \times 2^{-127}\) ,但很不幸,这应当是 +0
的编码。因此,我们才需要引入非规格化数,并且重新解释 exp
和 frac
。换句话说,如果我们不引入非规格化数,我们就无法在 float
中表示数值 0
,因为我们的 frac
总是隐含为 1.frac
。
Aside
为什么要强调是+0
呢?因为在「IEEE 754」标准当中,
+0
和-0
虽然都是0
,但在科学计算的某些特殊场合,它们是表示不同的含义的,因此给0
保留了这 2 种编码(可参考 CSAPP 的 2.4 小节)。
好了,我们回到 exp
的特殊情况:
exp = 0x00
:表示非规格化浮点数
此时,\(E = 1 - bias\) ,而不是 \(0-bias\) 。frac
应该解释为 0.xxxxx
这种形式,而不是 1.xxxxx
(后面解释原因)。实质上相当于小数点左移一位 ,指数 exp
加一作为「补偿」。
exp = 0xff
:表示 2 种特殊值
frac = 0
时,表示 \(+\infty\) 或者 \(-\infty\) ,与之做运算会 overflow 。例如,1.0/0.0
和 -1.0/0
属于这种情况。frac != 0
时,表示 NaN (Not A Number) 。例如,\(sqrt(-1), \infty \times 0, \infty - \infty\) 属于此类情况。引入「非规格化数」之后,就解决了 0
的编码问题。对于 0x00000000
,可得 exp = 0x00, frac = 0...0
,因此 \(E=1-127=-126\) ,所以 \(val = 0.frac \times 2^E = 0.0...0 \times 2^{-126} = 0\) 。
实际上,「非规格化数」的引入还有一个好处:实现最大非规格化数到最小规格化数的平滑转变 。
在 32 位 float
中,显然非规格化数的所有编码为:
s exp frac
0 0000 0000 0000 0000 0000 0000 0000 000 => 0
0 0000 0000 0000 0000 0000 0000 0000 001 => 0.00000000000000000000001
0 0000 0000 0000 0000 0000 0000 0000 010 => 0.00000000000000000000010
...
0 0000 0000 1111 1111 1111 1111 1111 111 => 0.11111111111111111111111
最低位不断加 1 ,对应的数值变化是 \(\frac{1}{2^{23}}\) ,这就使得非规格化数在 0 的附近是均匀分布的,具有 \(gradual \quad underflow\) 的性质(后续我打算做一个图示来说明为什么引入「非规格化数」之后,float
的数值变化会更平滑)。
标签:mes 浮点数 符号 double 就是 无符号 内存 sig class
原文地址:https://www.cnblogs.com/sinkinben/p/12387524.html