浮点数的表示

定点数把小数点钉死在一个约定位置上,于是范围和精度成了一对不可调和的矛盾:小数点靠左则精度高、范围小,靠右则范围大、精度低, 位字长里再怎么挪也只有 个可表示的值。

浮点数的思路是把小数点的位置也编进数里。同样是 32 位,浮点数用一部分位记”小数点在哪”(阶码),剩下的记有效数字(尾数)。可表示的值仍然只有 个,但它们不再等距分布——靠近 0 的地方密,远离 0 的地方疏。这才是浮点数的本质:不是”能表示更多数”,而是”把有限的表示能力按相对精度分配”。

机制

一般格式

  • :数符,1 位。
  • :尾数,定点小数,决定精度。
  • :阶码,定点整数,决定范围。
  • :基数,隐含约定,IEEE 754 中 。

阶码和尾数的位数分配是一个纯粹的权衡:在总位数固定时,阶码每多一位,范围按平方级扩大;尾数每多一位,精度翻一倍。所以 float 到 double 时阶码只从 8 位加到 11 位,尾数却从 23 位加到 52 位——范围已经够用了,精度才是瓶颈。

规格化:为什么必须有

同一个数有无数种写法:。若不加约束,编码就不唯一,比较大小和判等都无法进行。

规格化就是那条约束:要求尾数的最高有效位必须是有意义的。二进制下 时:

  • 尾数用原码表示时,规格化形式是 (小数点后第一位为 1)。
  • IEEE 754 采用另一种约定:(小数点前一位为 1)。

规格化还有一个实际收益,它是 IEEE 754 隐藏位的来源:既然规格化后最高位必然是 1,那这一位就不必存储了——用同样的位数多得到一位精度。这一位叫隐藏位。

IEEE 754 的字段划分

flowchart LR
    subgraph SP["单精度 float(32 位)"]
        direction LR
        S1["S<br/><b>1 位</b>"]:::s
        E1["阶码 E<br/><b>8 位</b>(偏置 127)"]:::e
        M1["尾数 M<br/><b>23 位</b>(+1 位隐藏位)"]:::m
    end
    subgraph DP["双精度 double(64 位)"]
        direction LR
        S2["S<br/><b>1 位</b>"]:::s
        E2["阶码 E<br/><b>11 位</b>(偏置 1023)"]:::e
        M2["尾数 M<br/><b>52 位</b>(+1 位隐藏位)"]:::m
    end
    SP ~~~ DP

    classDef s fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
    classDef e fill:#fef3c7,stroke:#d97706,color:#78350f
    classDef m fill:#dbeafe,stroke:#2563eb,color:#1e3a5f

字段顺序是 ,这个顺序不是随意的:把阶码放在尾数前面,使得同号浮点数的位串可以直接当整数比较大小。阶码用移码、放在高位,正是为了这个效果。

阶码的偏置为什么是

IEEE 754 的阶码是移码,但偏置取 (127 / 1023),而不是教材里移码的常规偏置 (128 / 1024)。这是本节最容易被忽略、又最容易被考的一处差异。

原因是阶码的全 0 和全 1 被保留作特殊用途,真正可用的阶码字段值是 ,对应实际指数 。

偏置取 127 而不是 128,让指数范围向正侧偏一格,收益很具体:最小正规格化数的倒数不会上溢。

可表示若偏置为:上溢

五类编码

阶码字段的取值决定了这串位属于哪一类,五类的规则互不相同,套错公式是最常见的错误:

阶码 E尾数 M类别值
任意规格化数
全 0非规格化数
全 0(有两个零)
全 1
全 1NaN非数

(表中数值为单精度;双精度把 、、。)

非规格化数的两个反直觉之处,都要单独记:

① 隐藏位是 0 不是 1。 阶码全 0 时不再假定 ,改用 。

② 实际指数是 而不是 。 这是刻意规定的,目的是让非规格化数与最小规格化数平滑衔接:最大的非规格化数是 ,紧挨着最小规格化数 ,中间没有断层。

非规格化数存在的意义叫逐级下溢:如果没有它,比 更小的数就只能直接变成 0,于是 a != b 却 a - b == 0 的怪事会发生。有了非规格化数, 总能得到一个非零的微小值。代价是非规格化数的有效位数逐渐减少,精度是递减的。

关键边界值

这几个值建议直接记住,真题里反复出现:

单精度 float位串值
最小正非规格化数0x00000001
最大非规格化数0x007FFFFF
最小正规格化数0x00800000
0x3F800000阶码 ,尾数全 0
最大规格化数0x7F7FFFFF
0x7F800000
NaN0x7FC00000 等
双精度 double值
最小正规格化数
最大规格化数
最小正非规格化数

有效位数:单精度 位二进制,,故约 7 位十进制有效数字;双精度 位,约 15~16 位。

编码与解码模板

十进制 → IEEE 754(四步):

  1. 转二进制:整数部分除 2 取余,小数部分乘 2 取整(2.1.1)。
  2. 规格化成 :小数点移到最高位 1 的右边,记下移动量 。
  3. 算阶码字段 ,转成 8 位二进制。
  4. 取尾数字段: 中小数点后的 23 位,不足补 0,超出要舍入。

例:。。, 1000 0010, 1001 后补 19 个 0。位串 1100 0001 0100 1000 0000 0000 0000 0000 0xC1480000。

IEEE 754 → 十进制(三步):

  1. 按 1/8/23 拆字段。
  2. 先看阶码属于五类中的哪一类——这一步绝不能跳过。
  3. 规格化数:。

边界

IEEE 754 的移码与教材移码的偏置不同

偏置阶码全 0 表示
教材标准移码(128)最小指数
IEEE 754 阶码(127)保留:非规格化数或 0

题目问”IEEE 754 的阶码用什么码”,答案是移码;问”偏置是多少”,答案是 127 而不是 128。 两问经常连着出现。

规格化 ≠ 精确

规格化只是编码唯一化的要求,与这个数能否被精确表示毫无关系。 规格化后是 ,尾数是无限循环的,存进 23 位必然被舍入。

“浮点数不精确”的根源在2.1.1 的进制转换,不在 IEEE 754 的位数分配。 增加尾数位只能减小误差,永远不能消除。

阶码决定范围,尾数决定精度——但都不是唯一因素

这句常见结论需要加限定:

  • 范围主要由阶码位数决定,但尾数也有微小贡献(最大值是 而非 )。
  • 精度由尾数位数决定,且这是相对精度:绝对精度随数值大小变化, 附近相邻两个可表示数相差 。

“浮点数在数轴上不等距”这一点是理解一切浮点问题的钥匙,包括为什么大数加小数会”吃掉”小数(见 2.3.2)。

零、无穷、NaN 的性质

特点
有两个零编码,但比较时 为真
由上溢或非零数除以 0 产生; 参与运算仍是
NaN由 、、 等产生;,与自身比较为假

” 成立当且仅当 是 NaN”,这是 C 里判断 NaN 的标准写法,也是高频判断题。

同一位串既是浮点数也是整数

0x3F800000 作为 float 是 ,作为 int 是 。两者毫无数值关系。

因此 (int)f 与 *(int*)&f 结果完全不同——前者是值转换,后者是位型重解释,见 2.1.4、2.3.3。

对照速查

floatdouble
总位数3264
阶码位数 / 偏置8 / 12711 / 1023
尾数字段 / 有效位23 / 2452 / 53
十进制有效数字约 7 位约 15~16 位
最小正规格化数
最大规格化数
阶码尾数类别
全 0
全 0非规格化数()
其余任意规格化数()
全 1
全 1NaN

考点

  • 阶码决定范围、尾数决定精度;double 主要加的是尾数
  • 规格化的目的是编码唯一,隐藏位是它的副产品,白得一位精度
  • IEEE 754 阶码用移码,偏置是 (127/1023),不是
  • 偏置取 127 使最小规格化数的倒数不上溢
  • 阶码全 0 时隐藏位为 0,实际指数取 而不是
  • 非规格化数实现逐级下溢,代价是精度递减
  • 五类编码必须先判类别再套公式
  • ;
  • 单精度约 7 位十进制有效数字,双精度约 15~16 位
  • 字段顺序 使同号浮点可按位串比较大小

链接