Appearance
第03章 计算机的算术运算
整数运算
加法与减法
- 溢出 (Overflow) 条件:
- 加法:当两个同号操作数相加,结果超出可表示范围时发生溢出。
- 两个正数相加,若结果符号为
1,则溢出。 - 两个负数相加,若结果符号为
0,则溢出。 - 一正一负相加,绝不会发生溢出。
- 两个正数相加,若结果符号为
- 减法:当两个异号操作数相减时可能发生溢出(同号相减无溢出)。
- 正数减负数,若结果符号为
1,则溢出。 - 负数减正数,若结果符号为
0,则溢出。
- 正数减负数,若结果符号为
- 加法:当两个同号操作数相加,结果超出可表示范围时发生溢出。
- 多媒体算术运算:
- 图形和媒体处理通常需要对8位和16位的向量数据进行操作。
- 采用具有分段式进位链的64位加法器,可同时对8个8位、4个16位或2个32位向量进行运算。
- 此技术被称为 单指令多数据 (SIMD, Single-Instruction, Multiple-Data) 或 子字并行 (Subword Parallelism)。
- 饱和操作 (Saturation):发生溢出时,结果保持为可表示的最大(或最小)值,而非像二进制补码那样进行取模回绕(常用于音频限幅、视频饱和处理)。
乘法
- 硬件实现与优化:
- 基础实现:被乘数左移,乘数右移,使用128位ALU和128位乘积寄存器。
- 优化实现:使用64位ALU,被乘数固定,乘积寄存器整体右移(乘法/移位并行执行,每次部分积相加占1个周期)。
- 高速实现:使用多个加法器并行执行并支持流水线化,权衡性价比。
- RISC-V 整数乘法指令集:
| 指令 | 用途 | 结果说明 | 溢出检查方法 |
|---|---|---|---|
mul | 乘法 | 返回乘积的低64位 | 结合高位指令检查 |
mulh | 有符号乘积高位 | 返回乘积的高64位(双有符号数) | 与 mul 结合检查64位溢出 |
mulhu | 无符号乘积高位 | 返回乘积的高64位(双无符号数) | 与 mul 结合检查64位溢出 |
mulhsu | 混合乘积高位 | 返回乘积的高64位(一有符号,一无符号) | 结合高位指令检查 |
除法
- 算法与硬件实现:
- 需要首先检查除数是否为
0。 - 除法恢复 (Restoring Division):先做减法,若余数小于0,则将除数加回以恢复原值。
- 有符号除法:使用绝对值相除,最后按规则调整商和余数的符号。
- 基础与优化硬件:基础版除数右移、商左移;优化版使用64位ALU,余数寄存器左移,可与乘法器共享硬件资源。
- 高速实现:除法无法像乘法那样完全并行(减法依赖余数符号),更快的除法器(如 SRT 除法)每步可产生多个商位。
- 需要首先检查除数是否为
- RISC-V 整数除法指令集:
| 指令 | 用途 | 异常处理机制 |
|---|---|---|
div | 有符号除法 | 溢出和除0 不产生错误,只返回预定义结果以保证执行速度 |
rem | 有符号求余数 | 同上 |
divu | 无符号除法 | 同上 |
remu | 无符号求余数 | 同上 |
浮点运算与 IEEE 754 标准
浮点数表示方法
- 基础格式:类似于科学记数法的二进制表示
。 - IEEE 754 标准:目前几乎全球通用,解决了科学计算代码的可移植性问题,分为单精度(C语言
float)和双精度(C语言double)。 - 编码结构:
- S (Sign):符号位(
0代表非负数,1代表负数)。 - Fraction (有效数字/尾数):规格化表示为
,小数点前隐含一位 1,无需显式存储(即 隐含位 (Hidden Bit))。 - Exponent (指数):采用 偏置表述 (Biased Notation),实际指数 = 寄存器指数 - 偏阶。
- S (Sign):符号位(
| 精度类型 | 总位数 | 偏阶 (Bias) | 有效数字精度 | 近似十进制精度 | 最小正值范围 | 最大正值范围 |
|---|---|---|---|---|---|---|
| 单精度 | 32位 | 127 | 23位尾数 ( | ~6位十进制数 | ~ | ~ |
| 双精度 | 64位 | 1023 | 52位尾数 ( | ~16位十进制数 | ~ | ~ |
特殊数值表述
- 非规格化数 (Denormalized Numbers):指数全为
0,此时隐含位也是0,允许渐进下溢以减少精度损失(注:此时0.0有两种正负表述)。 - 无穷大 (Infinity):指数全为
1,尾数全为0,表示,可用于后续运算避免溢出检查。 - NaN (Not-a-Number, 非数字):指数全为
1,尾数不全为0,表示非法或未定义结果(如0.0 / 0.0),可传递至后续运算。
浮点加法运算步骤
- Step 1. 对齐小数点:将指数较小的数向右移位,直到其指数与较大的数相同。
- Step 2. 尾数相加:将对齐后的有效数字进行加法运算。
- Step 3. 规格化与检查:规格化相加结果,并检查是否发生上溢 (Overflow) 或下溢 (Underflow)。
- Step 4. 舍入:对结果进行舍入处理,若舍入后破坏了规格化,则再次进行规格化。
- 硬件特性:浮点加法器远比整数加法器复杂,需要多个时钟周期完成,通常支持流水线化。
算术精确性与舍入控制
- 附加保护位:硬件运算时保留多余的位以提升精度,包括 保护位 (Guard)、舍入位 (Round) 和 粘性位 (Sticky)。
- 舍入模式 (IEEE 754-2008 定义 5 种):舍入到最近的偶数、舍入到最近的最大幅值、向0舍入、向
舍入、向 舍入。 - 精度重要性:浮点精度不仅对科学计算至关重要,也影响消费市场(如著名的 Intel Pentium FDIV 硬件漏洞)。
RISC-V 浮点指令与代码映射
寄存器与核心指令
- 寄存器约定:拥有独立的32个浮点寄存器
f0至f31。双精度占用整个64位,单精度数值保存在低32位中。浮点和整数操作之间严格分离。 - 指令汇总:
| 操作类型 | 单精度指令 (.s) | 双精度指令 (.d) | 说明 |
|---|---|---|---|
| 内存存取 | flw, fsw | fld, fsd | 单精度取/存字,双精度取/存双字 |
| 算术运算 | fadd.s, fsub.s, fmul.s, fdiv.s, fsqrt.s | fadd.d, fsub.d, fmul.d, fdiv.d, fsqrt.d | 浮点加/减/乘/除/平方根运算 |
| 逻辑比较 | feq.s, flt.s, fle.s | feq.d, flt.d, fle.d | 比较结果为0或1,保存在整数目的寄存器中,供 beq/bne 分支跳转使用 |
汇编代码映射示例
示例 1:将华氏度转换为摄氏度
- C 代码:
c
float f2c (float fahr) {
return ((5.0/9.0)*(fahr - 32.0));
}- RISC-V 汇编映射 (假定参数在
f10中,返回结果在f10中):
assembly
f2c:
flw f0, const5(x3) # f0 = 5.0f
flw f1, const9(x3) # f1 = 9.0f
fdiv.s f0, f0, f1 # f0 = 5.0f / 9.0f
flw f1, const32(x3) # f1 = 32.0f
fsub.s f10, f10, f1 # f10 = fahr - 32.0f
fmul.s f10, f0, f10 # f10 = (5.0f/9.0f)*(fahr-32.0f)
jalr x0, 0(x1) # return示例 2:双精度矩阵乘法 (部分循环逻辑)
- C 代码 (32x32 矩阵
$C = C + A \times B$):
c
void mm (double c[][], double a[][], double b[][]) {
size_t i, j, k;
for (i = 0; i < 32; i = i + 1)
for (j = 0; j < 32; j = j + 1)
for (k = 0; k < 32; k = k + 1)
c[i][j] = c[i][j] + a[i][k] * b[k][j];
}- RISC-V 汇编映射 (最内层循环片段,假定指针在
x10~x12,索引在x5~x7):
assembly
L3:
# 计算并加载 b[k][j]
slli x29, x7, 5 # x29 = k * 32 (行大小)
add x29, x29, x6 # x29 = k * 32 + j
slli x29, x29, 3 # x29 = 字节偏移 (乘8)
add x29, x12, x29 # x29 = b[k][j] 的地址
fld f1, 0(x29) # f1 = b[k][j]
# 计算并加载 a[i][k]
slli x29, x5, 5 # x29 = i * 32 (行大小)
add x29, x29, x7 # x29 = i * 32 + k
slli x29, x29, 3 # x29 = 字节偏移 (乘8)
add x29, x11, x29 # x29 = a[i][k] 的地址
fld f2, 0(x29) # f2 = a[i][k]
# 核心浮点运算
fmul.d f1, f2, f1 # f1 = a[i][k] * b[k][j]
fadd.d f0, f0, f1 # f0 = c[i][j] + a[i][k] * b[k][j]
# (注:此处可用 fmadd.d 单条指令替换 fmul.d 和 fadd.d)
# 循环控制
addi x7, x7, 1 # k = k + 1
bltu x7, x28, L3 # if (k < 32) go to L3
fsd f0, 0(x30) # 保存回 c[i][j]