定点数的乘除运算
乘除法在硬件上没有加减法那样的”一步到位”办法。加法可以靠先行进位压成常数级延迟,乘除法则本质上是一串加减与移位的迭代,
本节的两条主线:乘法怎样把”逐位相加”变成硬件能做的形式,除法怎样在”不能试错”的电路里做试商。
机制
乘法的位宽:
两个
由此得到一个在 C 里天天踩的坑:
int a = 100000, b = 100000;
long long c = a * b; /* 溢出:乘法在 int 域完成,结果 1410065408 */
long long d = (long long)a * b; /* 正确:10000000000 */乘法的结果类型由操作数决定,与赋值目标无关。 a * b 两个都是 int,乘法就在 32 位里做,高 32 位直接丢弃,之后再扩展已经晚了。硬件层面 x86 的 imul 其实同时产生了高低两半(放在 EDX:EAX),是 C 的类型规则丢掉了高半部。
手工乘法到机器乘法:三点改造
竖式乘法是”逐位判断 → 移位 → 全部加起来”。直接照搬到硬件有两个麻烦:需要
机器的做法是三点改造:
- 被乘数不左移,改为部分积右移。 这样加法器只需
位宽——每次都是”当前部分积的高半部 + 被乘数”。 - 每次只做一次加法,加完立刻右移,不积攒。
- 移出的低位直接进入乘积的低半部,乘数寄存器腾出的位置正好装它,乘数寄存器和乘积低半部共用一组触发器。
于是硬件只需要:一个
原码一位乘法:符号单独算
原码乘法把问题拆成两半,因为原码的符号位与数值位本来就是分离的:
数值部分按绝对值做
注意这里是逻辑右移(补 0),因为参与运算的是绝对值,全程为正,没有符号可保。
原码乘法的缺点很明显:符号要单独处理,需要额外的判断和最后的拼接,控制逻辑不统一。
补码一位乘法(Booth 算法)
Booth 的目标是让符号位也参与运算,一套流程走到底。
做法:在乘数最低位之后附加一位
| 动作 | |
|---|---|
00 | 部分积不变 |
01 | 部分积 |
10 | 部分积 |
11 | 部分积不变 |
然后算术右移一位(补符号位,不是补 0——这是与原码乘法的关键差别)。共做
为什么是相邻两位,为什么能处理负数,答案在一个恒等式里:10(触发减)、扫描到 1 段的上沿时看到 01(触发加),段内部一律 11(什么都不做)。Booth 算法就是这个恒等式的逐位实现。
负数能被自动处理,是因为补码的最高位权是 10 恰好贡献一个减法,正是那个负权。
验证一遍:0 1 0 1 0:
完整手算:
| 轮 | 动作 | ||||
|---|---|---|---|---|---|
| 初 | 0000 | 0101 | 0 | ||
| 1 | 10 | 0001 | 1010 | 1 | |
| 2 | 01 | 1111 | 0101 | 0 | |
| 3 | 10 | 0001 | 0010 | 1 | |
| 4 | 01 | 1111 | 0001 | 0 |
结果 1111 0001
关联对照:Booth 的"一加一减"与移位优化是同一个恒等式
编译器把
优化成 ,用的正是 ,即 。这与 Booth 算法处理连续 1 段的原理完全相同,只是一个发生在编译期、一个发生在运行时的硬件里。参见 2.2.2。
阵列乘法器:用面积换时间
一位乘法要
这是本章第三次出现”面积换时间”——先行进位加法器、桶形移位器、阵列乘法器,三者是同一个设计哲学的三次应用。
除法:位宽与前提
- 定点小数:要求
,否则商 ,超出定点小数范围。 - 定点整数:要求商在
位范围内。
不满足前提就是除法溢出,见下。
恢复余数法
模仿竖式除法:“试着减一下,减不动就还回去”。
每一轮:余数左移一位,减去除数。
- 结果
:够减,商上 1,余数保留。 - 结果
:不够减,商上 0,再把除数加回去(恢复余数),然后进入下一轮。
“加回去”这一步是纯粹的浪费:它不产生任何信息,只是撤销上一次操作。最坏情况下
加减交替法(不恢复余数法)
核心观察:如果这一轮减完是负数
而
于是规则简化成:
| 上一轮余数 | 商上 | 本轮动作 |
|---|---|---|
| 1 | 左移后减除数 | |
| 0 | 左移后加除数 |
“够减就减、不够减就加”,加减交替,故得名。 每轮固定一次加减,共
收尾要校正:最后一轮结束后,若余数为负,需再加一次除数才是真正的余数。这一步容易漏。
C 的整数除法与取整方向
C99 起明确规定:整数除法向零截断,取模的符号跟随被除数。(a/b)*b + a%b == a 在所有情况下成立。
这与算术右移的向负无穷取整不一致,是 2.2.2 已经点过的裂缝:
边界
除法溢出
除法是唯一会让机器真的停下来的定点运算。
加减乘溢出时机器只置 OF 然后继续(见 2.2.3),除法溢出则在 x86 上直接触发 #DE 除法错误异常,程序被内核终止或转入异常处理。
两种情形:
- 除数为 0。
- 商超出目标位宽。最典型的是
:数学结果 超出 int范围,触发异常。这正是补码非对称性的又一个后果。
“除以零在 C 里是未定义行为”和”机器上触发除法异常”是两个层面的说法,前者是语言不作承诺,后者是这台机器实际发生的事。
逻辑右移 vs 算术右移在乘法中的分工
这一条最容易记混:
| 算法 | 部分积右移方式 | 原因 |
|---|---|---|
| 原码一位乘 | 逻辑右移(补 0) | 运算的是绝对值,全程非负 |
| 补码一位乘(Booth) | 算术右移(补符号位) | 部分积是补码,可能为负 |
判据是”部分积有没有符号”,不是”被乘数有没有符号”。
乘法的高半部不是”溢出”
x86 提供两套指令:mul/imul 的单操作数形式产生完整的 EDX:EAX),双操作数形式只保留低 * 对应后者。
408 的考法与实际硬件的差距
真实 CPU 早已不用一位乘法:乘法用 Wallace 树 + 阵列,除法用 SRT 算法(一次出多位商)。教材讲一位乘除法,目的是把”加减 + 移位”这条主线讲清楚,不是描述现代实现。
考试实际会问的是:位宽关系、Booth 的判断表与步数、加减交替法的规则与校正、除法溢出条件、取整方向。完整手算全过程出现在真题里的频率不高,但判断表必须能背。
对照速查
| 原码一位乘 | 补码一位乘(Booth) | |
|---|---|---|
| 符号 | 单独异或 | 自动处理 |
| 判断依据 | 乘数最低 1 位 | 相邻 2 位 |
| 部分积右移 | 逻辑 | 算术 |
| 轮数 |
| 恢复余数法 | 加减交替法 | |
|---|---|---|
| 不够减时 | 加回除数再继续 | 不加回,下轮改为加 |
| 每轮加减次数 | 1~2 次 | 固定 1 次 |
| 收尾 | 无 | 余数为负时加一次除数 |
考点
的积是 位;C 中 int * int仍是int,需先转宽类型- 原码乘:符号异或 + 绝对值相乘 + 逻辑右移
- Booth:附加位
,看相邻两位, 01加 /10减 /00、11不动,算术右移 - Booth 的原理是
,一段连续 1 换成一加一减 - 加减交替法每轮固定一次加减,最后余数为负要加一次除数校正
- 除法溢出会触发异常,是唯一会中断程序的定点运算
溢出 - C 的整数除法向零截断,与算术右移的向负无穷不同
链接
- 🏠 返回总览:计算机组成原理第 2 章:数据的表示和运算总览
- ⬅️ 上一节:2.2.3 定点数的加减运算
- ➡️ 下一节:2.3.1 浮点数的表示
- 📖 名词库:第 2 章名词库