关于 CSAPP - Lecture 04 Floating Point 的浮点数(float)的笔记。
定义
IEEE 754 浮点数标准定义了浮点数的表示方法。
有分为单精度(32 位)和双精度(64 位)。

浮点数分规范化数(normalized numbers)、非规范化数(denormalized numbers)和特殊值三种情况。
对于规范化数,浮点数的表示为:
其中:
- s:符号位,0 表示正数,1 表示负数
- exp:编码后的指数值
- bias:指数值的偏移量,。对于单精度浮点数为 127(),对于双精度浮点数,bias 为 1023()
- frac:尾数部分,表示小数部分
对于非规范化数,浮点数的表示为:
特殊值包括:
- 正零和负零
-
- 正零为所有位均为 0
-
- 负零为 s 为 1,其它位均为 0
- 正无穷和负无穷:exp 全为 1,frac 全为 0
-
- 正无穷为 s 为 0,
-
- 负无穷为 s 为 1
- NaN(Not a Number):exp 全为 1,frac 不全为 0
观察到,浮点数可以省略 frac 的前导 1,这样可以节省存储空间。
但是这样的话无法表示 0,因为如果省略了前导 1,那么 frac 全为 0 时表示的数值为 ,永远不可能表示 0,所以需要规定当 时,非规范数的式子是 ,这样就可以表示 0 了。
同时,当 时,frac 全为 0 时为最大的非规范化数,发现对应的数值为 ,与最小的规范化数 时 之间的间隔过大,形成断层。
当 exp 为四位,frac为三位,举例说明:
最小的规范化数:
最大的非规范化数:
如果用的是 ,即:
- 值 =
- 差值 =
- 百分比为
如果用的是 ,即:
- 值 =
- 最小规范数仍为
- 差值 =
- 百分比为 ,差距过大。


在非规范化的情况,相邻浮点数间距是一致的。在规范化的情况下,随着 exp 的变化,浮点数间距也会变化。
舍入
浮点数在运算中可能会出现无法精确表示的情况,这时需要进行舍入。
IEEE 754 标准定义了四种舍入方式:
- 向最近偶数舍入(Round to Nearest, Even):将结果舍入到最接近的浮点数,如果结果正好在两个浮点数之间,则舍入到偶数。
- 向零舍入(Round toward Zero):将结果向零方向舍入。
- 向正无穷舍入(Round toward +∞):将结果向正无穷方向舍入。
- 向负无穷舍入(Round toward -∞):将结果向负无穷方向舍入。
默认情况下,IEEE 754 标准采用向最近偶数舍入方式。
实际运算的舍入中,需要考虑尾数后的额外三位 GRS(guard bit, round bit, sticky bit):
- G(Guard Bit):紧接在尾数后的第一位,用于判断舍入方向。
- R(Round Bit):紧接在 G 位后的第二位,也用于判断舍入方向。
- S(Sticky Bit):尾数后所有位的逻辑或结果,如果尾数后还有非零位,则 S 位为 1,否则为 0。
向最近偶数舍入以 GSR 为 100 为分界线,有三种情况:
- 如果 G 位为 0,则直接舍弃 GRS 位,保持尾数不变。对应 GSR 为 000、001、010、011 四种情况,小于 100。
- 如果 G 位为 1,且 R 位或 S 位至少有一位为 1,则将尾数加 1。对应 GSR 为 101、110、111 三种情况,大于 100。
- 如果 G 位为 1,且 R 位和 S 位均为 0,则检查尾数的最后一位:
- 如果尾数的最后一位为 0,则直接舍弃 GRS 位,保持尾数不变。
- 如果尾数的最后一位为 1,则将尾数加 1。对应 GSR 为 100 情况。
三位足以以判断舍入方式,先考虑两位和一位的情况下为什么不能够判断舍入方式:
-
只有一位 G 位:
- 如果 G 位为 0,则可以确定舍弃 G 位,保持尾数不变。
- 如果 G 位为 1,则无法确定是向偶数舍入还是向上舍入,因为可能是 100 或者 101 或者更大的情况。
-
只有两位 G 和 R 位:
- 如果 G 位为 0,则可以确定舍弃 GR 位,保持尾数不变。
- 如果 G 位为 1 且 R 位为 1,则可以确定向上舍入。
- 如果 G 位为 1 且 R 位为 0,则无法确定是向偶数舍入还是向上舍入,因为可能是 100 或者 101 或者更大且 R 位为 0 的情况。(即无法考虑 S 位及其后面的位)
在 G 位为 1 且 R 位为 0 的情况下,加入 S 位后,可以区分出 GRS 为 100(需要考虑尾数最后一位)和 GRS 为 101、110、111(需要向上舍入)的情况,则覆盖了所有情况,第四位没有必要。
浮点数加法
对于浮点数加法,需要对齐指数,然后进行尾数相加,最后进行规范化和舍入。
假设有两个浮点数 和 ,其中 的指数大于
-
对齐指数:将 的尾数右移 位,使得两个浮点数的指数相同。
-
尾数相加:根据符号位,进行尾数的加法或减法运算。
-
规范化:如果尾数的最高位为 2,则需要将尾数右移一位,并将指数加 1。如果尾数的最高位为 0,则需要将尾数左移,直到最高位为 1,并相应地减少指数。在此过程中,如果指数溢出,则结果为无穷大;如果指数下溢,则结果为零或非规范化数。
-
舍入:根据舍入规则,对结果进行舍入。
例子:
假设 ,。
-
对齐指数:将 的尾数右移 2 位,得到 。
-
尾数相加:。
-
规范化:将尾数右移一位,得到 。
-
舍入:根据舍入规则,对结果进行舍入,比如如果存入三位尾数,结果为 ,如果存入四位尾数,结果为 。
浮点数加法不符合结合律,因为在对齐指数时可能会丢失精度。
浮点数乘法
假设有两个浮点数 和 。
-
符号位相乘:结果的符号位为 。
-
尾数相乘:结果的尾数为 。
-
指数相加:结果的指数为
-
规范化:如果尾数的最高位为 2,则需要将尾数右移一位,并将指数加 1。在此过程中,如果指数溢出,则结果为无穷大。
-
舍入:根据舍入规则,对结果进行舍入。
同样,浮点数乘法不符合结合律,因为在尾数相乘时可能会丢失精度。
类型转换
浮点数与整数之间的转换需要考虑范围和精度的问题。
将 int 转换为 float
int 有 31 位有效位(符号位不算),float 有 23 位尾数,会丢弃掉低位数字,保留高位数字。
将 float/double 转换为 int
截断小数部分,保留整数部分。
如果 float/double 的值超出 int 的表示范围或者是 NaN,则结果未定义,通常会返回 INT_MIN。
将 int/float 转换为 double
int 转换为 double 不会丢失精度,因为 double 有 52 位尾数,足够表示 int 的所有有效位。
float 转换为 double 也不会丢失精度,因为 double 的尾数比 float 多,可以精确表示 float 的值。
Data representation 这几节课里面总是强调这些并非数学意义上的 number,而是一些二进制串,依赖具体定义的 representation 才能被解释为某个数值。