计算机系统导论 · 第三讲:浮点数

本页目录

第三讲
Floating Point

笔记整理自2026年9月14日课程

1. This lecture to cover:

  • Background: Fractional binary numbers
  • IEEE floating point standard: Definition
  • Example and properties
  • Rounding, addition, multiplication
  • Floating point in C
  • Summary

2. Fractional binary numbers

  • What is ?

  • Divide by 2 by shifting right
  • Multiply by 2 by shifting left
  • Numbers of form

Limitations

  • Can only exactly represent fractions of the form where k is an integer and n is a non-negative integer.
  • Just one setting of binary point within w bits.

3. IEEE 754 浮点数标准

IEEE 754 规定了浮点格式、特殊值、舍入方向、基本运算的结果以及异常状态。以下主要讨论最常见的二进制格式。一个有限数的数学形式为

其中 是符号位, 是有效数(significand), 是无偏指数。位串由三个字段组成:

符号位有偏指数域小数域(fraction field)

注意: 只保存有效数的小数部分;它并不总等于 。规格化数有隐含的首位 ,非规格化数有隐含的首位 。

3.1. 常见二进制格式

格式总位数精度 偏置
binary161615101115
binary32(C 的常见 float)32182324127
binary64(C 的常见 double)6411152531023
binary12812811511211316383

这里 包含规格化数不存储的隐含首位。binary32 约有 7 位可靠十进制有效数字,binary64 约有 15–16 位;这描述的是精度,不是数值范围。

例: binary32 的 。在区间 中,相邻可表示数相差 ;因此 的下一个较大浮点数是 。但在 中相邻数相差 。指数增大一,相邻数的绝对间距也扩大一倍。

3.2. 按指数域分类与解释规则

设指数域有 位、小数域有 位,

所有位模式都由下面的规则唯一分类:

类别数值或含义
零
次正规数
任意规格化数
无穷
NaN不是一个实数值

3.2.1. 规格化数(normal number)

规则:当 时,

隐含首位 不占存储位,所以 binary32 虽只有 23 位 frac,却有 24 位二进制精度。

例 1:编码十进制 为 binary32。

因此 ,,,并在小数域末尾补零:

0 10001100 11011011011010000000000

它是精确编码,因为整数的二进制有效位数没有超过 24。

例 2:解码 binary32 位模式 0xC0A00000。

其位串为 1 10000001 01000000000000000000000。于是

3.2.2. 次正规数(subnormal number)

旧资料也常称 denormalized number。规则:当 且 时,

指数不是 ,有效数也没有隐含首位 。这样,最大次正规数与最小规格化数之间仍保持同一间距,实现渐进下溢(gradual underflow)。

例 1: binary32 的最小正次正规数的 frac 只有最低位为 1,因此

例 2: 最大正次正规数为

最小正规格化数为 。二者之差正好是 ,与该区域相邻次正规数的间距一致。

例 3:解码 0x001C0000。 位串分段为

0 00000000 00111000000000000000000

,,所以

3.2.3. 有符号零

规则: 且 表示零; 得 , 得 。数值比较时 ,但其位模式和部分运算结果不同。

例:

  • ,而 ;
  • copysign(1.0, -0.0) 得到 ;
  • 通常的相等比较中,+0.0 == -0.0 为真。

有符号零能保存趋近方向或符号信息。例如一个极小负数在向零舍入后可成为 。

3.2.4. 无穷大

规则:指数域全 1 且小数域全 0。符号位分别给出 和 。

例:

  • 在默认舍入模式下,最大有限正数再乘 会发生上溢,通常得到 ;
  • ,;
  • ,但 没有确定的数值结果,得到 NaN。

3.2.5. NaN

规则:指数域全 1 且小数域非零。NaN 分为 quiet NaN(qNaN)和 signaling NaN(sNaN);具体如何用 frac 的最高位区分由格式约定。NaN 的其余小数位可作为 payload 保存诊断信息,但跨运算、语言或平台时不应假定 payload 必然保留。

例: 、(在实数浮点运算中)、、 都产生 NaN,并引发 invalid 异常。qNaN 通常继续传播而不重复触发 invalid;使用 sNaN 通常触发 invalid,并把结果静默化为 qNaN。

NaN 的比较规则很重要:除“不等于”外,普通有序比较都返回假。因此若 x 为 NaN,则 x == x 也为假,x < 0 与 x >= 0 同样都为假。检查 NaN 应使用语言提供的 isnan,而不是依赖大小比较。

3.3. 重要边界值

数值binary32binary64
最小正次正规数
最小正规格化数
最大有限数
的下一个较大数

machine epsilon 一词常指使 的最小间距,因此 binary32 常取 ,binary64 常取 。它不是整个数轴上的统一绝对误差上界。

3.4. 舍入规则

IEEE 754 的运算语义可理解为:先求无限精度的精确结果,再按目标格式与当前舍入方向得到可表示结果。2019 版标准定义五种舍入方向;常见硬件至少直接支持前四种。

3.4.1. 舍入到最近,正中取偶(roundTiesToEven)

选择距离精确值最近的可表示值;若恰好位于两个可表示值的中点,则选择最低保留位为 的那个。它是二进制运算的默认舍入模式。

十进制演示(保留到整数): ,,,,。最后三个中点例子中,结果均选择偶数。

二进制例子(保留小数点后两位):

  • 距 更近,结果为 ;
  • 距 更近,结果为 ;
  • 恰在 与 中间,后者最低位为 0,结果为 ;
  • 恰在 与 中间,前者最低位为 0,结果为 。

3.4.2. 舍入到最近,正中远离零(roundTiesToAway)

选择最近值;正中点选择绝对值较大的结果。这一方向是 IEEE 754-2008/2019 规定的属性之一,但并非所有二进制硬件环境都提供动态切换支持。

例(保留到整数): ,,,。非中点如 仍舍入为 。

3.4.3. 向零舍入(roundTowardZero)

选择绝对值不大于精确结果的、最接近零的可表示数;等价于丢弃超出精度的部分。

例(保留到整数): ,。发生正溢出时,该方向返回最大有限正数而不是 ;负溢出相应返回最小有限负数。

3.4.4. 向正无穷舍入(roundTowardPositive)

选择不小于精确结果的最小可表示数。

例(保留到整数): ,。注意它并非“增大绝对值”:负数结果会趋近于零。

3.4.5. 向负无穷舍入(roundTowardNegative)

选择不大于精确结果的最大可表示数。

例(保留到整数): ,。负数结果的绝对值可能增大。

3.4.6. Guard、Round 与 Sticky 位

硬件通常保留额外位来判断舍入。设最终保留位之后的第一位为 round bit,之后所有位的逻辑或为 sticky bit;一些教材把最终保留位称为 guard bit。对于正中取偶:

  • round bit 为 0:被删除部分小于半个单位,不增加保留部分;
  • round bit 为 1 且 sticky 为 1:大于半个单位,保留部分加 1;
  • round bit 为 1 且 sticky 为 0:恰好为中点,仅当最低保留位为 1 时加 1,使结果最低位变为 0。

例: 把 保留为 4 位有效数字(1.xxx)。保留部分是 ,round bit 为 1,其后还有 1,所以结果加一成为 。把 同样舍入时得到 ,此时还要右移并把指数加一,这一步称为舍入后的再规格化。

3.5. 五类浮点异常

IEEE 754 的异常通常设置状态标志,并交付标准规定的默认结果;是否陷入(trap)由运行环境决定。一个操作可能同时设置多个标志。

3.5.1. Invalid operation(无效运算)

精确结果没有可定义的浮点数值,默认结果通常是 qNaN。

例: 、、、、负数的实数平方根,以及把 sNaN 用作操作数。这里不是因为结果过大或过小,而是运算本身没有确定的实数或扩展实数结果。

3.5.2. Division by zero(除以零)

有限且非零的被除数除以精确的零时触发,默认结果是带正确符号的无穷。

例: ,,并设置 divideByZero 标志。 不属于此类,而属于 invalid。

3.5.3. Overflow(上溢)

舍入前的有限结果在目标格式中指数过大。默认正中取偶时,结果通常为相应符号的无穷,并同时设置 inexact;定向舍入可能返回最大有限数。

例: binary32 最大有限数 乘 。正中取偶得到 ;向零舍入得到最大有限 binary32。

3.5.4. Underflow(下溢)

有限结果非常小且发生精度损失。标准把 underflow 与 tininess、inexact 联系起来;实现可在舍入前或舍入后检测 tininess,因此只得到一个精确的次正规结果不一定设置 underflow。

例: binary32 的 位于 与最小正次正规数 的正中间。正中取偶得到 (零的最低位为偶),并设置 inexact 与 underflow。

3.5.5. Inexact(不精确)

舍入后的结果不等于无限精度的精确结果。这是最常见的异常标志。

例: binary32 中计算 。因为 的二进制展开无限循环,只能保存其邻近值,所以设置 inexact。若两个可表示数相加的精确结果仍可表示,例如 ,则不设置 inexact。

3.6. 基本运算规则

3.6.1. 加法与减法

设 ,,并假设 :

  1. 将较小指数的有效数右移 位,使二进制点对齐;
  2. 按符号对有效数加减;
  3. 将结果规格化;
  4. 按目标精度舍入;若舍入产生进位,再次规格化;
  5. 根据结果设置 overflow、underflow 或 inexact 等标志。

例(只有 4 位有效数字):

保留 4 位有效数字并正中取偶得到 。

浮点加法满足交换律(忽略 NaN payload 等表示细节),但不满足结合律。

反例: 在精度不足以保留 的低位时, 可得到 ,而 。

3.6.2. 乘法

有限非零数相乘时,精确中间结果满足

然后规格化、舍入并检查指数范围。

例(4 位有效数字):

保留 4 位有效数字,删除部分为 11,故舍入为 。

乘法不满足结合律,也不对加法满足分配律。

结合律反例: 在有限格式中, 可能先上溢为 ;而 可得到约 。

分配律反例: ,但 可能成为 。

3.6.3. 融合乘加(FMA)

fma(a, b, c) 计算 ,只在最终结果处舍入一次;普通写法 a*b+c 允许乘法结果先舍入,再进行加法。

例: 若 的精确值含有目标格式无法保存的低位,而这些低位会与 发生抵消,则 FMA 能在相加前保留它们,可能得到比两次舍入更准确、甚至不同的结果。因此编译器把 a*b+c 改成 FMA 会改变逐位结果,严格浮点语义下需要相应许可。

3.7. 比较、次序与代数性质

  • 除 NaN 外,有限数和无穷可按数值有序比较; 与 数值相等。
  • 任一 NaN 参与 <、<=、>、>=、== 时结果均为假;!= 为真。
  • IEEE 754 还定义 totalOrder,可以对 、、无穷和不同 NaN 表示建立全序;它不同于普通语言比较运算符。
  • 加法和乘法通常是交换的,但不结合;乘法不对加法分配。
  • 有限浮点运算不对实数运算封闭,因为结果可能为无穷或 NaN。

例: 若 x 为 NaN,条件 x < 1 || x >= 1 为假,而实数中对应命题恒真。这是因为 NaN 是 unordered,而不是“小于”或“大于”任何数。

3.8. 十进制数为何经常不能精确表示

有限二进制小数恰好是 形式。最简分数的分母若含有 2 以外的质因子,其二进制展开就无限循环。

例:

binary64 中通常保存的是邻近值

因此连续三次相加 0.1 + 0.1 + 0.1 不保证逐位等于由十进制 0.3 转换得到的浮点值。判断近似计算结果时应根据问题尺度选择绝对误差或相对误差容限,不能统一使用一个固定的小数常量。

4. C 中的浮点数

4.1. 类型与表达式

C 语言至少提供 float、double 和 long double,但语言标准本身不要求它们必须分别等同于 IEEE binary32、binary64 与某一固定扩展格式;应通过实现文档、sizeof、FLT_RADIX、FLT_MANT_DIG、DBL_MANT_DIG 等确认。

没有后缀的浮点常量如 0.1 通常具有 double 类型,0.1f 才是 float。许多表达式中 float 会经通常算术转换参与运算;可观察的求值精度还受实现、编译选项和是否使用 FMA 影响。

4.2. 类型转换规则与例子

4.2.1. 整数转浮点

若值可精确表示,则结果精确;否则舍入为相邻可表示值。binary32 具有 24 位精度,所以所有绝对值不超过 的整数都能精确表示,但更大的整数不一定能。

例: 16777216()能精确转为 binary32;16777217 夹在相邻可表示值之间,正中取偶时转为 16777216.0f;16777218 可以精确表示。

典型 32 位 int 的所有值都可精确转为 binary64,因为 binary64 有 53 位精度。

4.2.2. 浮点转整数

C 的转换丢弃小数部分,即向零截断。若有限值截断后不能在目标整数类型中表示,或源值是 NaN/无穷,则行为未定义;不能依赖某台机器恰好返回的值。

例: (int)3.9 得 ,(int)-3.9 得 。但 (int)1e100 或 (int)NAN 没有可移植结果。

4.2.3. 不同浮点格式之间

从较窄格式转到能精确包含其全部值的较宽格式通常精确;从较宽格式转到较窄格式可能舍入、上溢或下溢。

例: binary32 值先转 binary64 再转回 binary32,数值不变(NaN payload 等表示细节除外)。binary64 的 1.0 + 2^-30 转为 binary32 时会舍入为 1.0f,因为该增量远小于 binary32 在 1 附近的间距 。

4.3. 位模式解码的可靠做法

在 C 中不要用违反严格别名规则的指针强制转换读取浮点位模式。可使用 memcpy(C20 以前的通用做法)或实现提供的位转换工具。

#include <stdint.h>
#include <string.h>

float x = -5.0f;
uint32_t bits;
memcpy(&bits, &x, sizeof bits);
/* 在采用 IEEE binary32 的实现上,bits 为 0xC0A00000。 */

手工解码步骤是:取最高位为 ,接下来的指数位解释为无符号整数 Exp,其余为 frac;再按“按指数域分类与解释规则”中的表选择公式。必须先分类,不能对零、次正规数、无穷或 NaN 一律套用规格化公式。

4.4. 常见判断题

设 int x; float f; double d;,并假设 f、d 不是 NaN;在常见的 32 位 int、binary32 float、binary64 double 环境中:

  • x == (int)(float)x:不恒真。例:,转 float 后成为 。
  • x == (int)(double)x:恒真,因为 binary64 能精确表示所有 32 位整数。
  • f == (float)(double)f:对所有非 NaN float 恒真,扩大再缩回不会丢失原值。
  • d == (double)(float)d:不恒真,第一次转换可能丢失 binary64 的低位。
  • f == -(-f):对非 NaN 值恒真;两个负号也正确恢复有符号零。
  • 2/3 == 2/3.0:通常为假。左侧是整数除法,结果为 ;右侧为浮点除法,约为 。
  • d*d >= 0.0:在 d 不是 NaN 的前提下恒真。有限 d 的平方为非负有限数、正零或上溢后的正无穷;无穷的平方仍为正无穷。
  • (d+f)-d == f:不恒真。若 很大,加入较小的 时 会在第一次舍入中丢失。

4.5. 总结

  • 位模式必须先按 exp 与 frac 分类,再使用对应公式;规格化数和次正规数的隐含首位、指数规则不同。
  • IEEE 754 明确定义特殊值、舍入方向、默认结果与异常标志,使浮点行为可以独立于具体硬件进行推理。
  • 浮点运算相当于精确运算后舍入,但有限精度会破坏结合律与分配律。
  • 数值范围、有效精度和相邻数间距是不同概念;误差分析必须考虑数值所在的指数区间。
  • C 的类型不应未经确认就等同于某个 IEEE 格式,转换越界、NaN 比较和近似相等判断尤其需要显式处理。

查看原始笔记