第三章:计算机中的算术
一、整数表示(Integer Representation)¶
在计算机系统中,整数主要有两种表示方式:无符号整数和有符号整数(补码)。
1.1 无符号整数 (Unsigned Binary Integers)¶
无符号整数直接将二进制位权相加,仅用于表示非负数。
(1) 表示公式:
对于一个 \(n\) 位二进制数:
(2) 数值范围:
从 \(0\) 到 \(+(2^n - 1)\)
(3) 32位系统示例:
范围:\(0\) 到 \(+4,294,967,295\)。
二进制转十进制:例如 0000...1011₂ 对应 \(1\times2^3 + 0\times2^2 + 1\times2^1 + 1\times2^0 = 8 + 0 + 2 + 1 = 11_{10}\)。
1.2 补码有符号整数 (2s-Complement Signed Integers)¶
补码是计算机中表示有符号(正、负、零)整数的标准方法,其核心特点是最高位(MSB)具有负权重。
(1) 表示公式:
对于一个 \(n\) 位数:
(2) 符号位 (Sign Bit):
在32位整数中,第31位是符号位:
0:表示非负数(Non-negative numbers)。
1:表示负数(Negative numbers)。
(3) 数值范围:
从 \(-2^{n-1}\) 到 \(+(2^{n-1} - 1)\)。
(4) 32位系统示例:
范围:\(-2,147,483,648\) 到 \(+2,147,483,647\)。
计算示例:\(1111...1100_2\) 对应 \(-1\times2^{31} + 1\times2^{30} + \dots + 1\times2^2 + 0\times2^1 + 0\times2^0 = -4_{10}\)。
补码的关键特性:¶
- 非负数一致性:非负数的补码表示与其无符号表示完全相同。
- 特定的重要数值(\(N\)位):
- 0:\(0000 0000 ... 0000\)
- -1:\(1111 1111 ... 1111\)(所有位全为1)
- 最大正数 (Most-positive):\(0111 1111 ... 1111\) (\(2^{N-1}-1\))
- 最小负数 (Most-negative):\(1000 0000 ... 0000\) (\(-2^{N-1}\),其绝对值比最大正数大1)
- 符号位扩展:同一个数值在不同位宽表示下,低位数值位完全相同,符号位扩展。例如,\(-20\) 的
- 8 位表示:\(\textcolor{red}{1110\ 1100}_2\)
- 16 位表示:\(1111\ 1111\ \textcolor{red}{1110\ 1100}_2\)
- 32 位表示:\(1111\ 1111\ 1111\ 1111\ 1111\ 1111\ \textcolor{red}{1110\ 1100}_2\)
1.3 符号取反操作 (Signed Negation)¶
在补码系统中,将一个数变号(正数变负数或反之)的捷径是:求反加一。
(1) 操作步骤:
- 取反 (Complement):将所有位翻转(1变0,0变1)。
- 加1:在最低位加1。
(2) 数学原理:
证明:
对于一个 \(n\) 位数:
\(x = \color{red}{-x_{n-1}2^{n-1}} \color{black}{+ x_{n-2}2^{n-2} + \dots + x_{1}2^1 + x_{0}2^0}\)
我们将其各位取反:
\(\bar{x} = \color{red}-{\bar{x}_{n-1}2^{n-1}} \color{black}{+ \bar{x}_{n-2}2^{n-2} + \dots + \bar{x}_{1}2^1 + \bar{x}_{0}2^0}\)
注意到:
\(x_{k} + \bar{x}_{k} = 1\)
因此我们有:
证毕。
(3) 示例:将 +2 变为 -2:
- \(+2\) =
0000...0010₂ - 第一步取反:
1111...1101₂ - 第二步加1:
1111...1101₂ + 1 = 1111...1110₂(即 -2)。
进而引申:
Theorem 1.
计算机中,减去一个正数,等于加上它的相反数的补码
Theorem 2.
计算机中,加法和减法都可以通过补码的加法实现
二、整数加减法与溢出处理¶
2.1 整数加减法 (Integer Addition & Subtraction)¶
- 加法原则:从最低位开始逐位相加,并向高位进位(Carries)。
- 减法原则:通过加法器实现。减去一个数等于加上该数的补码(即取反加1)。
- 例如:\(7 - 6 = 7 + (-6)\)。
2. 溢出处理 (Dealing with Overflow)¶
当运算结果超出了当前位数所能表示的范围时,发生溢出。
- 加法溢出判定:
- 一正一负相加:永远不会溢出。
- 两个正数相加:结果符号位为1(变为负数),则溢出。
- 两个负数相加:结果符号位为0(变为正数),则溢出。
- 减法溢出判定:
- 两个同号数相减:永远不会溢出。
- 负数减正数:结果符号位为1,则溢出。
- 正数减负数:结果符号位为0,则溢出。
- 两种溢出处理方式
- Satruating(饱和):当发生溢出时,结果被强制设置为可表示的最大值或最小值。常见于Graphics and media processing
- Clipping(截断):当发生溢出时,结果被截断为可表示的范围内的值。即取模回绕(Wrap-around)。例如在8bit无符号加法中,\(255 + 1\) 会回绕到 \(0\)。
三、乘法运算及其电路实现¶
3.1 基础乘法算法 (Long-multiplication Approach)¶
类似于手工长乘法,通过逐位检查乘数(Multiplier)并对被乘数(Multiplicand)进行移位和累加。
- 硬件特点:乘积的长度是操作数长度之和(如两个32位数相乘得到64位积)。
- 初步硬件实现:包含64位ALU、64位被乘数寄存器(左移)、32位乘数寄存器(右移)及64位乘积寄存器。缺点:时钟周期多,硬件利用率低。

3.2 优化后的乘法器电路 (Optimized Multiplier)¶
为了减小面积和提升速度,芯片设计中常采用优化电路:
- 并行化设计:将加法和移位步骤并行执行。
- 寄存器优化:乘积寄存器的右半部分最初存放乘数,随着运算进行,乘数被移出,乘积的低位被移入,从而节省空间。

3.3 高性能“真实”乘法器 (Real Multiplier Structure)¶
现代芯片(如GPU/高性能CPU)中使用更复杂的电路结构:
- Booth Recoding:通过对乘数进行编码,减少产生的部分积(Partial Products)数量。
- Wallace Tree/Dadda Tree:采用树状结构的加法器阵列,实现部分积的并行压缩相加,大幅降低延迟。
- 超前进位加法器 (Carry Look-ahead Adder, CLA):用于最后的求和阶段(CPA),以极快速度处理进位传递。

3.4 RISC-V 乘法指令实现¶
由于 RISC-V 的标准寄存器(在 32 位架构下)只能存储 32 位数据,因此指令集通过不同的指令来分别获取这 64 位结果的低 32 位和高 32 位。
(1) 获取乘积的低位 (Least-significant 32 bits)
- 指令:
mul rd, rs1, rs2 - 功能:将
rs1和rs2相乘,并将结果的低 32 位写入目标寄存器rd。 - 特点:无论操作数是有符号数还是无符号数,乘积的低 32 位在二进制补码表示下都是相同的,因此不需要区分符号类型。
(2) 获取乘积的高位 (Most-significant 32 bits)
对于乘积的高 32 位,符号位的影响至关重要,因此 RISC-V 提供了三条不同的指令来处理符号扩展:
-
mulh rd, rs1, rs2:用于 有符号数 × 有符号数。它会将结果的高 32 位存入rd。 -
mulhu rd, rs1, rs2:用于 无符号数 × 无符号数。它将结果的高 32 位作为无符号数处理并存入rd。 -
mulhsu rd, rs1, rs2:用于 有符号数 × 无符号数。这种混合模式在处理多倍精度算术或特定算法时非常有用,它同样取结果的高 32 位。
四、整数除法(Division)运算及其电路实现¶
4.1 基础除法算法与恢复余数法 (Restoring Division)¶
除法的逻辑过程类似于手工长除法,但硬件实现更为具体:
- 基本原则:对于 \(n\) 位操作数,会产生一个 \(n\) 位的商(Quotient)和一个 \(n\) 位的余数(Remainder)。
- 算法步骤:
- 检查除数(Divisor)是否为0。
- 比较除数与被除数(Dividend)的当前位,如果除数 \(\le\) 当前位,则商置1并进行减法。
- 否则,商置0,并将被除数的下一位降下继续比较。
- 恢复余数法 (Restoring Division):在硬件执行减法后,如果余数变为负数(< 0),则必须将除数加回到余数中以“恢复”原来的值。
4.2 除法器硬件电路实现 (Division Hardware)¶
课件对比了两种硬件结构,重点在于资源优化:
- 非优化结构:使用64位ALU、64位余数寄存器和64位除数寄存器(右移),商寄存器为32位(左移)。

- 优化后的除法器 (Optimized Divider):
- 硬件高度复用:优化后的除法器结构与乘法器非常相似,通常可以使用同一套硬件来实现乘法和除法。
- 寄存器配置:32位ALU;64位余数寄存器初始化时右半部分放被除数,运算结束后左半部分存放余数,右半部分存放商。

4.3 除法加速技术 (Faster Division)¶
与乘法不同,除法很难使用类似华莱士树的并行硬件来加速。常用的加速手段包括:
(1) 不恢复余数法 (Non-restoring Division):
我们就假设减掉除数后余数小于0的情况
| Method | reminder of cycle \(N\) | reminder of cycle \(N + 1\) |
|---|---|---|
| Restoring | \(r - d(<0)\) restore to \(r\) | \(r \times 2 - d\) |
| Non-restoring | \(r - d(<0)\) | \(2(r-d) + d\) |
(上面的乘2,原因是在做下一轮减法时,需要将余数左移一位,这样被除数的下一位才能落在“个位”,可以联想一下手工除法以作理解)
Non Restoring 的意思就是:
即使减法结果为负,也直接存储该差值,不进行“加回”操作。
通过在下一周期进行特定的补偿调整,节省了恢复余数所需的时钟周期。
(2) SRT 除法 (SRT Division):
每一步可以产生多个商位(例如 Radix-4 每步产生4位)。
实现原理:利用查找表(Lookup Table),根据余数的高几位和除数的高几位来“预测”需要减去的数值,从而减少总迭代步数。

- SRT 算法中:查找表会存储 \(m\) 位的部分商,这些部分商是由部分余数的 \(m\) 位和除数的 \(m\) 位共同决定的。表格的存储空间大小为 \(2^{2m}\times m\) 位,这个大小会受到内存容量的限制。
- 采用基数为 \(2b\) 的除法运算,可以将所需的计算周期数降至原来的 \(1/b\),但每个周期的实现复杂度也相应增加了。
4. 有符号除法 (Signed Division)¶
有符号除法的核心原则是“余数的符号必须与被除数一致”:
- 取被除数和除数的绝对值进行除法运算。
- 调整符号规则:
- 如果操作数异号,则商为负。
- 余数的符号必须与被除数一致。
- 这意味着商总是向零舍入(Rounded toward zero)。
5. RISC-V 除法指令实现¶
RISC-V 将商和余数的获取拆分为不同的指令,且区分有符号与无符号:
- 获取商 (Quotient):
-
div rd, rs1, rs2:有符号除法。 -
divu rd, rs1, rs2:无符号除法。
-
- 获取余数 (Remainder):
-
rem rd, rs1, rs2:有符号取模。 -
remu rd, rs1, rs2:无符号取模。
-
五、浮点数表示与编码 (Floating-Point Representation)¶
浮点数用于表示非整数,包括极大或极小的数值,其形式类似于科学计数法。
5.1 二进制科学计数法 (Binary Scientific Notation)¶
在二进制中,浮点数表示为:\(\pm 1.xxxxxxx_2 \times 2^{yyyy}\)。
- 规格化 (Normalized):基数点左侧只有一位非零数字(在二进制中恒为1)。
- 非规格化 (Not normalized):如 \(+0.002 \times 10^{-4}\)。
5.2 IEEE 754 浮点数标准¶
这是目前计算机系统通用且几乎唯一采用的浮点数格式。它定义了两种主要表示形式:
- 单精度 (Single precision, float):32位。
- 双精度 (Double precision, double):64位。
编码结构图示:¶
| 精度 | 符号位 (S) | 指数位 (Exponent) | 尾数位 (Fraction) |
|---|---|---|---|
| 单精度 | 1 bit | 8 bits | 23 bits |
| 双精度 | 1 bit | 11 bits | 52 bits |
5.3 核心计算公式¶
浮点数 \(x\) 的数值计算如下:
关键编码原则:¶
- 符号位 (S):0 表示非负,1 表示负数。
- 隐含位 (Hidden Bit):为了节省空间,规格化后的尾数总是以“1.”开头,因此硬件编码时只存储点后的部分 (Fraction),计算时自动恢复“1.”。
- 偏置指数 (Biased Exponent):
- 指数部分采用偏置计数法,确保指数始终为无符号数,便于硬件比较大小。
- Bias 值:单精度为 127,双精度为 1023。
- 实际指数 = 寄存器值 (Exponent) - Bias。
Question
为什么需要bias?
在浮点数表示中,指数 \(E\) 本身既可能是正数,也可能是负数(例如 \(2^{-126}\) 或 \(2^{127}\))。如果直接用补码表示,硬件进行比较和排序时会变得复杂。
通过引入偏置 \(B\),我们将实际指数 \(e\) 映射为存储的指数 \(E\):
这样我们能够确保 \(E\) 始终是一个非负整数。
为什么是127和1023?
以单精度为例,规格化数(normalized numbers) 可用的 \(E\) 的范围是 \([1, 254]\)。为了让正负指数的范围尽可能对称,我们需要寻找一个中间值 \(B\),使得:当 \(E = 1\) 时,对应的实际指数 \(e_{min}\) 为负数。当 \(E = 254\) 时,对应的实际指数 \(e_{max}\) 为正数。且 \(e_{max} \approx |e_{min}|\)。
代入公式即可解得:\(B = 127\)
5.4 数值范围与精度¶
(1) 单精度范围
- 最小值:\(\color{blue}{1.00\cdots 0 \times 2^{1-127}} \color{black}{= \pm 1.0 \times 2^{-126} \approx \pm 1.2 \times 10^{-38}}\)。
- 最大值:\(\color{blue}{1.11\cdots 1 \times 2^{254-127}} \color{black}{\approx \pm 2.0 \times 2^{+127} \approx \pm 3.4 \times 10^{+38}}\)。
(2) 浮点数稠密度
浮点数不是均匀分布的。它是一个非均匀的线性分段结构。
单精度浮点数的形式为:\((-1)^S \times (1.M) \times 2^{E-127}\)
- 指数 (E) 决定了“量级”(区间)。
- 尾数 (M) 决定了该区间内的“划分密度”。
在一个给定的指数 \(E\) 下,尾数有 23 位,意味着该区间内有 \(2^{23}\) 个等间距的点。随着 \(E\) 的增大,区间的跨度变大,但点数始终为 \(2^{23}\),因此相邻点的间距(即最小精度单位,称为 ULP,Unit in the Last Place)会随指数增大而按指数增长。
(3) 相对精度
- 单精度:约为 \(2^{-23} \approx 1.19 \times 10^{-7}\),即大约 7 位十进制有效数字。
- 双精度:约为 \(2^{-52} \approx 2.22 \times 10^{-16}\),即大约 16 位十进制有效数字。
(4) 浮点数相对间距
对于指数 \(E\)(规格化数),相邻两个浮点数之间的差值为:
-
例子 1:指数相同时
假设 \(E= e + 127\)(即实际指数为 \(e\))。
此时 \(2^{e} \times 2^{-23} = 2^{e-23}\)。
在这个区间内,每个数之间都隔着 \(2^{e-23}\)。 -
例子 2:指数相差 1 时(跨过 \(2^n\) 分界点)
这是浮点数最容易让人误解的地方。当指数 \(E\) 增加 1 时,该区间的数值跨度翻倍,因此 ULP 也翻倍。- 在区间 \([2^0, 2^1)\),即 \(E=127\) 时,ULP 为 \(2^{-23}\)。
- 在区间 \([2^1, 2^2)\),即 \(E=128\) 时,ULP 为 \(2^{1-23} = 2^{-22}\)。
结论:数值越大,绝对误差越大,但相对精度(精度与数值的比值)保持不变。
5.5 非规格化浮点数¶
5.5.1. 数学定义:为什么偏偏是 \(2^{-126}\)?¶
前面我们提到,当指数位 \(E = 0\) 且尾数位 \(M \neq 0\) 时,该数为非规格化数。
它的计算公式是:
你可能会问:既然 \(E=0\),偏置是 \(127\),按照公式 \(e = E - B = 0 - 127 = -127\),为什么非规格化数的指数被硬性规定为了 \(-126\) 呢?
这是一个极度聪明的工程妥协,目的是为了与“最小的规格化数”实现无缝的平滑衔接。
我们来看看这中间的过渡:
-
最小的正规格化数(\(E=1, M=00...0\)):
数值 \(= 1.0 \times 2^{1-127} = 1.0 \times 2^{-126}\) -
最大的非规格化数(\(E=0, M=11...1\)):
数值 \(= 0.111...1_2 \times 2^{-126}\) (注意这里使用的是 \(-126\))
如果我们将非规格化数的指数设定为 \(-126\),那么从最大的非规格化数加 1 个 ULP(也就是加 \(0.000...1_2 \times 2^{-126}\)),刚好就等于 \(1.0 \times 2^{-126}\),完美过渡到了最小规格化数!
如果使用 \(-127\),这两者之间就会出现断层。因此,IEEE 754 规定非规格化数的实际指数“冻结”在最小规格化数的指数上(即 \(-126\)),仅仅将隐含位从 1 变成了 0。
5.5.2 核心使命:拯救“突然下溢”与捍卫数学公理¶
在没有非规格化数的早期计算机中,浮点数运算存在一个致命缺陷。
灾难场景:Flush-to-Zero(粗暴归零)
如果没有非规格化数,单精度能表示的最小正数就是 \(N_{min} = 1.0 \times 2^{-126}\)。
如果进行减法 \(x - y\),比如:
\(x = 1.0000001_2 \times 2^{-126}\)
\(y = 1.0000000_2 \times 2^{-126}\)
很明显 \(x \neq y\)。但是它们相减的结果是 \(0.0000001_2 \times 2^{-126}\)。
由于没有非规格化数(无法表示隐含位为 0 的数),计算机只能认定这个结果超出了表示范围(下溢),直接把它暴力截断为 0。
这违背了代数中最基本的一条公理:
\(x - y = 0 \iff x = y\)
在数值分析中,如果 \(x \neq y\) 但 \(x - y = 0\),会导致诸如 if (x != y) z = 1 / (x - y) 这样的代码发生除零崩溃(Divide by Zero)。
救星:逐渐下溢(Gradual Underflow)
非规格化数填补了 \(0\) 到 \(N_{min}\) 之间的巨大鸿沟。
这 \(2^{23} - 1\) 个非规格化数,像极其细密的沙子一样填进了最靠近 0 的区间。
由于非规格化数的存在,只要 \(x\) 和 \(y\) 是浮点数,\(x - y\) 的结果即使再小,也必定能用非规格化数精确表示出来。这就彻底保卫了 \(x - y = 0 \iff x = y\) 这个定理。
关于非规格化浮点数,更详细的介绍可以参考非规格化浮点数介绍文档
5.5.3 \(\pm 0\)¶
非规格化浮点数中,当尾数全为0时,表示正零(\(+0\))或负零(\(-0\)),取决于符号位。
5.6 Infinity and NaN¶
当指数全为1(\(E=255\))时,IEEE 754 预留了两种特殊数值:
- 无穷大 (\(\pm \infty\)):当尾数全为0时,表示正无穷或负无穷(取决于符号位)。这通常用于表示溢出结果或除以零的情况。
- NaN (Not a Number):当尾数非零时,表示非法数值(如 \(0.0/0.0\))。NaN 具有特殊的传播规则:任何与 NaN 进行的算术运算结果仍然是 NaN。
5.6 特殊数值汇总 (Reserved Values)¶
| 指数 (Exponent) | 尾数 (Fraction) | 表达对象 (Object) |
|---|---|---|
| 0 | 0 | 0.0 (有 \(\pm 0.0\) 之分) |
| 0 | 非零 | 非规格化数 (Denormal):用于表示极小值,允许精度阶梯式下降,此时\(e = -126(-1022)\) |
| 1 至 254 (2046) | 任意值 | 规格化浮点数 |
| 255 (2047) | 0 | 无穷大 (\(\pm \infty\)):用于处理溢出,无需检查即可继续运算 |
| 255 (2047) | 非零 | NaN (Not a Number):表示非法结果(如 0.0/0.0) |
六、浮点数加减法运算 (Floating-Point Addition & Subtraction)¶
浮点数的加减法比整数加减法要复杂得多,因为它们的小数点(量级)是不对齐的。其核心思想类似于科学计数法的加法(如:\(9.999 \times 10^1 + 1.610 \times 10^{-1}\))。
浮点数加减法(硬件执行)通常包含以下 四个标准步骤:
6.1 算法步骤 (Algorithm Steps)¶
(1) 比较指数并对齐尾数 (Compare and Align)
- 硬件操作:将两个操作数的指数相减,找出较小的一个。
- 对齐原则:小阶向大阶看齐。将指数较小的那个数的 尾数(连同隐藏位 1) 向右移位(Right Shift),每右移一位,其指数加 1,直到两个指数相等。
- 注意:右移过程中移出的低位数据可能会丢失,但硬件通常会保留几个保护位(后续详述)以保证精度。
(2) 尾数相加/相减 (Add/Subtract Significands)
- 在指数对齐后,直接将两个带隐藏位的尾数(24位或53位)放入加法器/减法器中进行运算。
(3) 规格化结果 (Normalize Result)
- 相加/相减后的结果可能不再符合 \(1.M\) 的规格化形式,需要进行调整:
- 如果结果 \(>2\)(例如 \(1.1_2 + 1.1_2 = 11.0_2\)):将尾数向右移 1 位,同时指数加 1。
- 如果结果 \(<1\)(例如相减产生 \(0.0011_2\)):将尾数向左移位,直到最高位变成 1,左移了几位,指数就减几。
- 在此阶段,硬件还要检查溢出 (Overflow)(指数过大)或下溢 (Underflow)(指数过小,变为非规格化数或0)。
(4) 舍入 (Rounding)
- 由于硬件位宽限制,规格化后的结果必须被截断/舍入到 23 位(单精度)或 52 位(双精度)。
- 舍入后可能会再次破坏规格化(例如进位导致变成 \(10.0_2\)),如果发生这种情况,需要再次右移并调整指数。
6.2 浮点加法器硬件电路特点¶
浮点加法器的电路比整数 ALU 复杂且庞大:
- 包含用于指数比较的减法器。
- 包含用于尾数对齐和规格化的两个移位器 (Shifters)。
- 包含一个宽带位的尾数加法器。
- 通常由于逻辑级数过深,浮点加法在高性能流水线 CPU 中会分为多个时钟周期(Pipeline Stages)完成。
七、浮点数乘除法运算 (Floating-Point Multiplication & Division)¶
相比于加法,浮点数乘法不需要对齐尾数,但在指数处理上有一个极易踩坑的重点:Bias的处理。
7.1 浮点乘法算法步骤¶
(1) 计算新指数并处理 Bias (Add Exponents)
对于两个浮点数 \(x_1\) 和 \(x_2\),实际指数相乘等于相加:\(e_{result} = e_1 + e_2\)。
但是在 IEEE 754 中,寄存器里存的是带偏置的指数 \(E\)(\(E = e + Bias\))。
如果直接把两个寄存器的 \(E\) 相加:
结果多出了一个 Bias!
因此,浮点乘法计算指数的公式为:
(2) 尾数相乘 (Multiply Significands)
- 将两个带隐含位的尾数(\(1.M_1\) 和 \(1.M_2\))当作无符号整数送入乘法器(如我们之前学过的华莱士树乘法器 )。
- 单精度中,24 位 \(\times\) 24 位会产生 48 位的中间乘积。
(3) 规格化 (Normalize)
- 因为 \(1 \le 1.M < 2\),所以两数相乘的结果范围是 \(1 \le Product < 4\)。
- 因此,乘积的最高有效位要么在常规位(格式为 \(1.xxxx\)),要么向前进了一位(格式为 \(1x.xxxx\))。
- 调整:如果是 \(1x.xxxx\),只需将结果右移 1 位,同时指数加 1即可。
(4) 舍入并确定符号 (Round and Sign)
- 按照 IEEE 754 标准舍入。
- 符号位直接通过异或逻辑门得出:\(\mathbf{S_{result} = S_1 \oplus S_2}\)。
八、浮点运算的硬件精度:GRS 位 (Guard, Round, Sticky) 与舍入规则¶
8.1 GRS 位的定义与作用¶
在对齐(右移)或乘法运算中,尾数低位被移出后,如果直接丢弃,会造成极大的精度损失。为了硬件能够按照 IEEE 754 标准进行精确的舍入(Rounding to nearest even,向偶数舍入),硬件 ALU 在尾数运算时会向右多扩展 3 个附加位:
- 保护位 (Guard bit, G):尾数右边第一位。对齐时被移出尾数的第一位数据会落入 G。
- 舍入位 (Round bit, R):G 右边的一位。被移出的第二位数据落入 R。
- 粘性位 (Sticky bit, S):R 右边的所有位。它的机制非常特殊:只要有任何一个
1被移出到它的位置(或它的右边),S 就会被永久置为1并锁死;只有当移出的全都是0时,S 才为0。- 数学公式:一旦有
1移入 \(S\),则 \(S = 1\)。
- 数学公式:一旦有
可以把 G, R, S 三位组合看作是尾数最低有效位(LSB, Unit of Last Place)右侧的小数:
-
GRS = 0xx(即 \(G=0\)):代表丢弃的部分 \(< 0.5\) LSB(距离下舍入的值更近)。 -
GRS = 100:代表丢弃的部分 恰好等于 \(0.5\) LSB(处于两个可表示值的正中间,即 Tie 状态)。 -
GRS = 1xx(且 \(R\) 或 \(S\) 至少有一个为 1):代表丢弃的部分 \(> 0.5\) LSB(距离上舍入的值更近)。
8.2 舍入规则 (Rounding Rules)¶
IEEE 754-2008 定义了 5 种舍入模式。在体系结构考试中,最常考也最复杂的是第一种(默认模式):
| 舍入模式 (Rounding Mode) | 英文名称 | 核心规则与 GRS 行为 |
|---|---|---|
| 1. 偶数优先舍入 (默认) | RNE (Round to Nearest, ties to Even) | 舍入到最接近的值。如果是 Tie(\(GRS = 100\)),则舍入到尾数最低位(LSB)为 0 (偶数) 的那一边。 |
| 2. 四舍五入 (远离 0) | RNA (Round to Nearest, ties away from zero) | 舍入到最接近的值。如果是 Tie,一律向远离 0 的方向舍入(即绝对值变大)。 |
| 3. 向零舍入 (截断) | RTZ (Round toward Zero / Truncate) | 一律直接丢弃 G, R, S 保护位,尾数保持不变。 |
| 4. 向正无穷舍入 (朝天) | RUP (Round toward \(+\infty\)) | 若为正数且 \(GRS > 0\),尾数加 1;若为负数,直接截断(绝对值变小)。 |
| 5. 向负无穷舍入 (朝地) | RDN (Round toward \(-\infty\)) | 若为负数且 \(GRS > 0\),尾数加 1(绝对值变大);若为正数,直接截断。 |
8.3 RNE(Round to Nearest, ties to Even) 的处理流程¶
- 若
GRS < 100(即 \(G=0\)):直接截断(尾数不变)。 - 若
GRS > 100(即 \(G=1\) 且 \(R \| S == 1\)):尾数加 1。 - 若
GRS == 100(Tie,正好一半):- 若尾数最低位为
0(已经是偶数):直接截断(保持偶数)。 - 若尾数最低位为
1(奇数):尾数加 1(使其变为偶数0)。
- 若尾数最低位为
8.4 运算示例¶
为了最清晰地展示这个过程,我们假设一个4 位有效尾数(1 位隐含的整数 + 3 位小数,即格式为 1.xxx)的简化浮点系统。
计算:\(1.000_2 \times 2^5 - 1.001_2 \times 2^1\)
第一步:对齐阶数 (Align Exponents)¶
为了进行减法,我们需要将阶数小的数据右移,使其阶数对齐到 \(2^5\)。
- 操作数 \(X = 1.000_2 \times 2^5\) (不需要移动)
- 操作数 \(Y = 1.001_2 \times 2^1\) (需要右移 \(5 - 1 = 4\) 位)
右移过程中 G, R, S 位的变化追踪:
- 初始 \(Y\) 尾数:
1.001 - 右移 1 位:
0.100,此时被移出的1进入 \(G\) \(\rightarrow\)0.100 | G=1, R=0, S=0 - 右移 2 位:
0.010,先前的1移入 \(R\) \(\rightarrow\)0.010 | G=0, R=1, S=0 - 右移 3 位:
0.001,先前的1移入 \(S\) \(\rightarrow\)0.001 | G=0, R=0, S=1 -
右移 4 位:
0.000,尾数末尾的另一个1移入 \(G\)。先前移入 \(S\) 的1依然保存在 \(S\)。
最终对齐后的 \(Y\) 为:\[Y = 0.000_2 \times 2^5 \quad (\mathbf{G=1, R=0, S=1})\]
第二步:尾数减法 (Significand Subtraction)¶
我们带上 G, R, S 位进行高精度减法:
X: 1.000 | 0 0 0 (GRS)
- Y: 0.000 | 1 0 1 (GRS)
-----------------------
结果: 0.111 | 0 1 1 (GRS) <-- 结果为 0.111_011_2 * 2^5
第三步:规格化 (Renormalization)¶
因为结果为 0.111...,不满足 1.xxx 的规格化要求。我们需要将其左移 1 位,同时将阶数减 1。
- 左移 1 位后,原来的 \(G\)(
0)被移入尾数的最低位。 - 原来的 \(R\)(
1)移入 \(G\),\(S\)(1)移入 \(R\)。 -
规格化后的中间结果为:
\[\text{Fraction} = 1.110_2 \times 2^4 \quad (\mathbf{G=1, R=1, S=0})\](注意:这就是为什么我们需要 R 和 S 位的深层次原因。左移后,原来的保护位补了上来,我们依然拥有高精度的舍入信息。)
第四步:执行 RNE(偶数优先)舍入¶
此时我们的尾数最后一行为 1.110,其保护位为 GRS = 110。
- 评估 GRS:因为 \(G=1, R=1, S=0\),所以 \(GRS = 110\)。
- 判断大小:\(110 > 100\),说明丢弃的部分大于 \(0.5\) LSB(更靠近大数一侧)。
-
舍入操作:执行上舍入 (Round Up),将尾数最低位加 1:
\[1.110 + 0.001 = 1.111\]
最终计算结果:¶
输出值存入寄存器:\(1.111_2 \times 2^4\)
Tie 状态(\(GRS = 100\))的“奇偶舍入”补充实例¶
如果上述减法算出来的最终中间结果是:
此时 \(GRS = 100\),处于 Tie 状态(正中间)。
- 看尾数的 LSB:当前尾数
1.110的最后一位是0(偶数)。 - 舍入决定:为了保持偶数,我们选择下舍入(截断)。
- 最终结果:\(1.110 \times 2^4\)。
而如果中间结果是:
此时同样是 \(GRS = 100\)。
- 看尾数的 LSB:当前尾数
1.111的最后一位是1(奇数)。 -
舍入决定:为了使其变为偶数,我们选择上舍入(加 1):
\[1.111 + 0.001 = 10.000\]此时产生进位,需要重新规格化:\(1.000 \times 2^5\)。
这种向最接近的偶数舍入的机制,能够在连续的大量浮点数运算中,使得正向误差和负向误差相互抵消,从而将系统的整体统计偏差(Bias)降到最低。
九、RISC-V 浮点指令集架构 (RISC-V FP Architecture)¶
RISC-V 将浮点功能作为可选扩展(Extensions):
- F 扩展:单精度浮点(32位)。
- D 扩展:双精度浮点(64位)。
9.1 独立的浮点寄存器堆 (Separate FP Register File)¶
RISC-V 的浮点计算不使用整数寄存器(x0-x31),而是有一套完全独立的 32 个浮点寄存器:f0 到 f31。
- 在 D 扩展中,每个寄存器宽 64 位(可以存放双精度);在 F 扩展中只使用低 32 位。
- 注意:
f0不是像x0那样硬连线为 0,它就是一个普通的浮点寄存器。
9.2 核心汇编指令分类¶
在 RISC-V 汇编中,浮点指令通常带有 .s (Single) 或 .d (Double) 后缀以指明数据类型。
(1) 浮点加载与存储 (Load & Store)
需要从内存加载到浮点寄存器,或反之(基址寻址的基址寄存器仍然是整数寄存器 rs1)。
-
flw fd, offset(rs1):Load Word (单精度)。 -
fsw fs2, offset(rs1):Store Word (单精度)。 -
fld / fsd:双精度加载/存储。
(2) 浮点算术运算 (Arithmetic)
-
fadd.s fd, fs1, fs2:浮点加法fd = fs1 + fs2 -
fsub.s fd, fs1, fs2:浮点减法 -
fmul.s fd, fs1, fs2:浮点乘法 -
fdiv.s fd, fs1, fs2:浮点除法 -
fsqrt.s fd, fs1:计算平方根(微架构中常用牛顿迭代法或SRT硬件实现)。
(3) 浮点比较与分支 (Comparison & Branching)
RISC-V 的优雅设计:结果写回整数寄存器
与其他架构(如x86有专门的条件码寄存器)不同,RISC-V 的浮点比较指令会将布尔结果(1 表示 True,0 表示 False)写入整数寄存器 rd,然后程序可以使用普通的整数分支指令(如 beq, bne)来进行跳转。
-
feq.s rd, fs1, fs2:如果fs1 == fs2,则整数寄存器rd = 1,否则rd = 0。 -
flt.s rd, fs1, fs2:Less Than (小于)。 -
fle.s rd, fs1, fs2:Less or Equal (小于等于)。
代码示例:判断 f1 和 f2 是否相等,如果不等则跳转到 Label
feq.s x5, f1, f2 # 如果 f1 == f2,将整数 1 存入 x5;否则存 0
beq x5, x0, Label # 如果 x5 等于 0 (即比较结果为假),则跳转到 Label
(4) 浮点与整数的转换 (Conversion)
用于在浮点寄存器和整数寄存器之间转换数据格式。
-
fcvt.w.s rd, fs1:将单精度浮点数转换成 32 位整数(w代表 word),写入整数寄存器rd。 -
fcvt.s.w fd, rs1:将 32 位整数转换成单精度浮点数。
Enjoy Reading This Article?
Here are some more articles you might like to read next: