%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A - 关于补码 - 设计补码的主要目的是什么?有何优势? - 对于一个给定的正数或负数,如何怎么得到其补码表示? - 补码的表示方式是如何推导/设计出来的? - 给点一个二进制补码表示,如何求得其表示的真值? - 补码之间的运算是如何进行的? - 补码的溢出 - 如何判断补码运算是否会溢出? - 加法溢出、减法溢出、乘法溢出,需要采取不同的判断逻辑 - 如何快速求出补码运算溢出时的溢出结果? #### 已明确 ![[02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示#^i6lffr]] #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% # 真值 "真值" 指一个数**在数学上的实际值**,即**正数/负数**分别用 $+/-$ 符号表示。 <br> # 原码 原码是对有符号整数的一种**二进制表示方式**,在**给定数据位宽**下: - 最高位为 **符号位**(0 表示非负,1 表示负); - 其余位为 **数值位**,即 **=="真值绝对值"的二进制表示==**。 - 0 有两种表示: - $+0$: $[0000\;...\;0000]$ - $-0$: $[1000\;...\;0000]$ > [!caution] 原码的缺点 > > 1. **加减运算复杂**:在原码表示下,**加法和减法运算需要根据符号位分别处理**,不能直接利用标准的二进制加法器,这在硬件实现上增加了复杂性。 > 2. **存在+0 和-0 的问题**:在某些情况下可能导致比较和运算的混乱 ### 原码的数值表示范围 给定数据位宽 $w$,原码所能表示的**有符号整数范围**为 $[-2^{w-1}+1,\;2^{w-1}-1]$: | | 可表示的数值范围 | 可表示的数值总数 | | --- | :---------------: | :-----------: | | 正数 | $(0, 2^{w-1}-1]$ | $2^{w-1}-1$ 个 | | 负数 | $[-2^{w-1}+1, 0)$ | $2^{w-1}-1$ 个 | | 0 | | 1 | | | | 共 $2^{w}-1$ 个 | - 最大值:$2^{w-1}-1$ ,表示为 $[0111\;...\;1111]$ - 最小值:$-2^{w-1}+1$,表示为 $[1111\;...\;1111]$ > [!example] > > 以 8 位系统下原码所能表示的最大值 $+127$ 与最小值 $-127$ 为例,其原码表示如下: > > - $+127:\;\;[0111\;1111]$ > - $-127:\;\;[1111\;1111]$ > <br><br><br> # 反码 反码是对有符号整数的一种**二进制表示方式**,在**给定数据位宽**下: - 最高位为**符号位**(0 表示非负,1 表示负),其余位为**数值位**。 - 正数的反码:即为其**二进制表示**; - **负数**的反码:将其 **=="绝对值"的二进制表示全部取反得到==** - **0 有两种表示**: - $+0$: $[0000\;...\;0000]$ - $-0$: $[1111\;...\;1111]$ ### 反码转真值 - 对于正数,其反码即为真值本身的二进制表示; - **对于负数,将反码全部取反得到==真值的绝对值的二进制表示==,再将其解释为负数。 也可用下列方法求反码的真值:**各个二进制位 0/1 与对应的 2 的幂次方相乘,其中高位权重取为 $-(2^{w-1}-1)$**,而后累加各项之和。 ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-35126E00D49F8A8D881AE88D74E1B0DF.png|575]] ### 反码的数值表示范围 给定数据位宽 $w$,反码所能表示的**有符号整数范围**为 $[-2^{w-1}+1,\;2^{w-1}-1]$: | | 可表示的数值范围 | 可表示的数值总数 | | --- | :---------------: | :-----------: | | 正数 | $(0, 2^{w-1}-1]$ | $2^{w-1}-1$ 个 | | 负数 | $[-2^{w-1}-1, 0)$ | $2^{w-1}-1$ 个 | | 0 | | 1 | | | | 共 $2^{w}-1$ 个 | - 最大值:$2^{w-1}-1$ ,表示为 $[0111\;...\;1111]$ - 最小值:$-2^{w-1}+1$,表示为 $[1000\;...\;0000]$ > [!example] > > 以 8 位系统下反码所能表示的最大值 $+127$ 与最小值 $-127$ 为例,其反码表示如下: > > - $+127:\;\;[0111\;1111]$ > - $-127:\;\;[1000\;0000]$ > <br><br><br> # 补码 > 补码(Two's Complement) 补码是一种**用于表示==有符号整数==的二进制编码方式**。现代计算机系统中均使用**补码**来表示**有符号整数**。 ## 补码表示的由来 ⭐ ### <font color="#c0504d">❓为什么需要补码?</font> 根据冯·诺依曼提出的经典计算机体系结构框架,计算机中的运算器**只有加法运算器**,**没有减法运算器**(据说曾尝试过减法运算器,但由于硬件开销太大而不可行)。所以,**计算机中不能直接做减法**。为此,需要**设计一种能够==将减法转换成加法运算==的二进制表示方法**。这正是采用补码的原因。 在补码表示下,**"减法" 可以等价转化为 "加法" 实现**,因而**可由==加法器来执行加法和减法==,简化计算机算术逻辑单元(ALU)的设计**。 ### <font color="#c0504d"> ❓ 补码是如何设计出来的?</font> **补码** (Two's Complement) 是基于**模运算**系统中的 "**补数**" 设计出来的编码[^1]。 在 **模 $m$ 运算系统** 中,两个自然数间的减法 $a-b$ 可**等价转换为加法**,存在以下同余关系: $ \begin{align} a-b &\equiv a+b_{\text {补数 }}\;\;(\bmod m) \\ -b &\equiv b_{\text {补数 }}\;\;(\bmod m) \end{align} $ 其中, $b_{补数}$ 为 **==模 $m$ 下非负数 $b$ 的补数==**,等于 **==模 $m$ 减去 $b$ ==** 得到。 > [!example] > 模 12 系统中,有 $-4 \equiv 8 \quad(\bmod 12)$,则 **$8$ 称之为 $-4$ 的补码**(也称补数)。 > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-4AB74BB8A3E561C0A83C40F680974D25.png|350]] **位宽为 $w$ 的二进制系统**,也即是一个**模 $2^w$ 运算系统**, **二进制减法**可转换为**等价的二进制加法**。 (二进制加法**只会保留 $w$ 位而==忽略最高位的进位==**,相当于做了 **$\text{mod} \;\;2^w$**) 因此,对于**负数真值 $-b$**,用其**在模 $2^w$ 系统下的==补数 $b_{补数}$ 的二进制表示==** 作为其 "**==补码表示==**": $ \begin{align} -b &\equiv b_{\text {补数 }}\;\;(\bmod 2^w)\\ [-b]_{补} &=b_{补数}\;\;\; \text{(位宽} w\text{下)} \end{align} $ 由此,**对 $b$ 的二进制减法**都**等价于==加上 $-b$ 的补码表示==** $[-b]_{补}$(即 $b_{补数}$): $ \begin{aligned} a-b & \equiv a+[-b]_{\text {补}}\;\left(\bmod 2^w\right) \\ & \equiv a+b_{\text {补数 }}\;\;\;\left(\bmod 2^w\right) \end{aligned} $ > [!NOTE] "**补码表示**" 正是由此设计的: > > 二进制位宽 $w$ 下,将其中**一半的位模式** (对应真值 $b_{补数}$)**用作负数 $-b$ 的补码表示**(负数范围 $[-1, -2^{w-1}]$),另一半用于**表示非负数值 $[0, 2^{w-1}-1]$** > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-0A8C2F16188416CE7C241A49F206939A.png|295]] > > 由此: > > - 1)**每个负数都有自己的补码表示**,补码表示下的 **==正、负数之间可以直接相加==**; > - 2)**减法都统一转换为等价的加法执行**, $a-b \equiv a+[-b]_{\text {补 }}\left(\bmod 2^w\right)$ > > 例如: > > - `7 - 5` 只需要用 `7` 的补码和 `-5` 的补码做二进制加法,结果即为 `2` 的补码。 > - `3 - 6` 只需要用 `3` 的补码和 `-6` 的补码做二进制加法,结果即为 `-3` 的补码。 > [!NOTE] 补数 > > 在数学上,"**补数**"(Complement)是对于 **==给定的进位制==**,**相加后能==使得自然数 $a$ 的位数增加 1== 的最小的数**[^2]。 > ——**$a$ 的补数即是给定进制下**,与 $a$ 相加后**能让最高位产生进位的最小数。** > > 设 $d$ 进制下使用 $n$ 位来表示自然数 $a$ ,则 **$a$ 的补数 $a_{补数}=d^n - a$**,称之为 "**$d$ 进制 n 位宽下 a 的关于基数 $d$ 的补数**"。 > (也即==**模 $d^n$ 系统**中 **$a$ 的补数**==) > > 例如,对于自然数 2: > > - 在**二进制**且**位宽为 2**时,2 的补数是 2; 2+2 将由二进制位的最高位产生一个进位 > - 在**十进制**且**位宽为 1** 时,2 的补数是 8; 2+8 将由十进制位的最高位产生一个进位 > - 在**十二进制**且**位宽为 1** 时,2 的补数是 10; 2+10 将由十二进制位的最高位产生一个进位 > - 在**十六进制**且**位宽为 1** 时,2 的补数是 14(D); 2+D 将由十六进制位的最高位产生一个进位 <br><br> ## 补码表示的格式 给定数据位宽 $w$ 下,**补码**表示规则如下: > [!summary] 补码表示规则 > > 补码: $[X]_{\text {补 }}=\left(2^w+X \right) \bmod 2^w,\;\;\;\;-2^{w-1} \leq X<2^{w-1}$,其中 $X$ 是真值 > > - 最高位为 **==符号位==**(0 表示非负,1 表示负),其余位为**数值位**。 > > - 正数的补码:即为二进制表示本身。 > > - **负数**的补码:可由**三种**方式得到 > 1. 将其 "**绝对值**" 的二进制表示 **==全部取反==后+1**; > 2. 将其 "**绝对值**" 的二进制表示==**保留最低位的 1 及右侧 0 全不变**==,而 **==将高位全部取反==**。 > 3. 用 "**模**" 减去**其绝对值**得到 **==补数==**,**补数的二进制表示**即为该负数的补码 > - 例如 8 位下,$-1 \equiv 255 \quad(\bmod 2^8)$,因此 **$-1$ 的补码**等于**255 的二进制表示**。 > > > - **0 只有一种表示**:$[0000\;...\;0000]$ > > > 其中,**负数的补码** = **给定位宽下==其绝对值的补数的二进制表示==**, > 即 $[-b]_{补} =b_{补数}\;\;\; \text{(位宽} w\text{下)}$ 。 > > 因此,"求负数补码" 的方式实际上是给定二进制位宽下 "**==求负数的绝对值的补数==**" 的方式。 > > ^looeif ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-896AB3244CF2E602F86C9114666E4872.png|344]] <br> ## 真值与补码间的转换 ### 真值转补码 ![[02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示#^looeif]] > [!example] > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-3D5FE96A2550909DCC80EE8AAE573F4E.png|633]] > > 对于真值 $-52$,其绝对值为 $52$, > 而 $52\;(00110100)$ 在 8 位二进制位宽下的 "**补数**" 为 $204\;(11001100)$ , > 这一**补数 $204$ 的二进制表示 $[11001100]$** 即作为**负数 $-52$ 的 "补码表示"**。 > 即 $[-52]_{补}=52_{补数}=204_2=[11001100]$ > > [!example] > $[-123]_{补数}=123_{{补数}}=133_{2}=128_{2}+5_{2}=[10000101]$ <br> ### 补码转真值 > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-3ACF417E03225D42796A6B77CAC6DC73.png|457]] #### 方式一:通用(理论上的求法) 补码表示中,**在给定数据位宽下,==最高位==(符号位)为 "==负权=="(negative weight)**。 **将给定补码转为真值**,**只需将补码表示下各个二进制位 0/1 乘上对应的 2 的幂次方后进行累加(其中==符号位的权值取为"负"==)即可**,如下图所示: ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-B2C509379B185C6EDC9269C5EDD659C3.png|559]] > [!note] 补码表示下各个二进制位的权重示意图 > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-2B527236702283FACB76D716B8A872B1.png|325]] #### 方式二(针对负数) $[-b]_{补} =b_{补数}\;\;\; \text{(位宽} w\text{下)}$ ,因此对该负数 "**补码**" 再求 "**补数**",即可得到 **==负数的绝对值== $b$**,添上负号即为**补码对应的真值**。 求 "**补数**" 的三种方法参见上文。 > [!example] > 例如,给定 8 位的补码 $[1111 1011]$,求其对应真值: > > 1. **确定符号**:最高位是 1,所以这是一个负数的补码。 > 2. 方式二:**保留最低位的 1 及其右侧 0 不变,而反转其余所有高位**,得到 $[00000101]$ 为负数真值的绝对值),因此真值是 $-5$。 > 3. 方式三:**用模 256 减去该补码对应的补数值 251**,得到该补数的补数 $b=5$(负数真值的绝对值),因此真值为 $-5$. > <br><br> ## 补码的数值表示范围 位宽 $w$ 下,**补码**所能表示的**有符号整数范围**为 $[-2^{w-1},\;\;2^{w-1}-1]$:**一半的位模式表示负数**,**另一半非负**。 | | 可表示的数值范围 | 可表示的数值总数 | 边界值 | | --- | :--------------: | :-----------------: | ---------------------------------------- | | 正数 | $(0, 2^{w-1}-1]$ | $2^{w-1}-1$ 个 | 最大值:$2^{w-1}-1$,位模式为 $[0111\;...\;1111]$ | | 负数 | $[-2^{w-1}, 0)$ | $2^{w-1}$ 个 | 最小值:$-2^{w-1}$,位模式为 $[1000\;...\;0000]$ | | 0 | | 1 | | | | | ==**共 $2^{w}$ 个**== | | > [!info] 不同位宽下补码的表示范围 > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-8EAC1E609744E2681D57E24F62DD0B13.png|700]] - 单字节 8 位,数值位为 7 位,补码表示的整数范围为 $[-2^7, 2^7-1]$,即 $[-128,127]$ - 双字节 16 位,数值位为 15 位,补码表示的整数范围为 $[-2^{15},2^{15}-1]$,即 $[-32768,32767]$ - 四字节 32 位,数值位为 31 位,补码表示的整数范围为 $[-2^{31},2^{31}-1]$,即 $[-2147483648, 2147483647]$ - 八字节 64 位,数值位为 63 位,补码表示的整数范围为 $[-2^{63},2^{63}-1]$。 ```text // 补码表示 Int32.MaxValue = 2^31 - 1 = 01111111111111111111111111111111 1 = 00000000000000000000000000000001 0 = 00000000000000000000000000000000 -1 = 11111111111111111111111111111111 Int32.MinValue = -2^31 = 10000000000000000000000000000000 ``` <br><br> ## 补码的优点 采用补码系统进行表示,具有以下**优点**: - **补码表示下,"==减法==" 运算都可通过 "==补码的二进制加法==" 运算完成**,**==能够直接处理负数==**: - 例如,$5-5$ 可表示为 $5+(-5)$,二者对应的**补码表示直接相加**即为算术运算结果:<br>$5\;(0101) + (-5)\;(1011) = 0\;(0000)$ - **补码本身**支持 "**==算术右移==**" 操作 - 例如,$-8\;(1000)>>1$ 得到 $1100$,即为 $-4$ 的补码; - 例如,$-6\;(1010)>>1$ 得到 $1101$,即为 $-3$ 的补码。 - 例如,$-3\;(1101)>>1$ 得到 $1110$,即为 $-2$ 的补码(下取整,向负无穷取); - **==便于溢出判断==**:当**运算结果超出表示范围**时,**补码可以自然地回绕**,通过**观察操作数和结果的符号位即可判断是否溢出**,而不需要额外的溢出检测逻辑。 - **正溢出**:两个正数相加得到一个负数。 - **负溢出**:两个负数相加得到一个正数。 ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-F8DA2CD9859822492B6075587E69DB22.png|425]] #### 补码的其他特点 - **唯一的零表示**:在补码中 $0$ 只有唯一的表示(所有位都是 0)。(而原码和反码有 $+0$ 和 $-0$ 之分) - **范围不对称**:给定数据位宽 $w$,补码能表示的范围是 $[-2^{w-1},\;\;2^{w-1}-1]$。 (而原码和反码是对称范围) - 例如,8 位二进制能表示的范围是 $-128$ 到 $+127$ 。 - **模运算特性**:补码的加减运算等同于**模 $2^n$ 的加减运算**,这使得溢出处理变得自然且一致,硬件实现也更简单。 - **转换简便**:**正数到负数(或反之)的转换只需取反加一**,不需要单独的减法电路。 <br><br><br> ## 补码运算 ### 补码表示下的运算结果 ⭐ 在**给定位宽 $w$ 下**,**采用补码表示的有符号数之间的算术运算**,实质上是**两个有符号数对应的补码的==二进制位模式==之间的 "==二进制运算=="**, 并**对二进制运算结果==截断保留 $w$ 位==**,再**将所得的==二进制位模式==作为 "==补码==",==解释为对应真值==。** 其中,给定位宽 $w$ 下 **==二进制位模式之间==(而不是真值之间)的运算** 等价于 **==模 $2^w$ 运算==**: **运算结果保留 $w$ 位即等价于 $\mod 2^w$。** 因此,**$w$ 位补码表示下**两个**有符号数 $x$ 与 $y$** 之间的**算术运算结果**,可按以下方式直接求得: 1. **计算十进制下算术运算的 "==预期结果==" (真值 $r$) 并取 $\mod 2^w$**,得到**二进制位模式本身对应的十进制数 $d$**; - 在模 $m$ 运算里,"取模" 始终都是**取正余数**,即如果为负就一直加到正。 $ \begin{align} r &= x\;\; \text{op}\;\;y \\ \\ d &= r\;\;\text{mod}\;\; 2^w \;\;(0\le d\le 2^{w}-1)\\ \end{align} $ 2. **将 $w$ 位的二进制位模式 $d$ 作为 "补码" ,解释得到对应真值** - 若 **$d$ 大于 $w$ 位补码可表示的最大值 $2^{w-1}-1$** ,则**减去 $2^w$**,得到**补码对应的真值 $T=d-2^w$;** - 若 **$d$ 小于 $w$ 位补码可表示的最小值 $2^{w-1}$** ,则**加上 $2^w$**,得到**补码对应的真值 $T=d+2^w$** $ T=x\;\;\text{op}^t_w\;\;y= \begin{cases} & d, \;\;\;\;\;\;\;\;\;\;\;\text{ if }\;-2^{w-1} \le d \le 2^{w-1}-1 \\\ & d-2^w,\;\;\;\text{ if }\; d>2^{w-1}-1\;\;\;(正溢出) \\ & d+2^w,\;\;\;\text{ if }\; d<-2^{w-1}\;\;\;\;\;\;\;(负溢出) \end{cases} $ 其中: - $r = x\;\; \text{op}\;\;\;\;y$ 为两个**十进制真值 $x$ 和 $y$ 算术运算的直接结果** - $T=x\;\;\text{op}^t_w\;\;y$ 为两个**十进制真值 $x$ 和 $y$ ==在 $w$ 位补码表示下==进行相同运算所得的结果 > [!example] 8 位宽补码表示下,有符号数之间的运算示例: > > - $4\;*^T_8\;127=>-4$ > - 十进制下 $4*127=508$ ,二进制截断保留 8 位得 $508\%256=252\;[1111\;1100]$,作为补码解释为 $-4$($252-2^8$) > > - $1\; +^T_8\;127=>-128$ > - 十进制下 $1+127=128$,位模式为 $128\;[1000\;0000]$,作为补码解释为 $-128$($128-2^8$)。 > > - $-1\;-^T_8\;(-128)=>127$ > - 十进制下 $-1-(-128)=127$,位模式为 $127[0111\;1111]$,作为补码解释为 $127$ 。 > > <br> ### 补码加法 **==补码加法==**(给定位宽下):**将两个操作数的补码表示==直接做二进制加法并舍弃最高位进位==,再将所得的二进制位模式按补码解释为对应真值。 $ [X+Y]_{补码}=[X]_{补码}+[Y]_{补码} $ ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-72A2C3D88B776FD546DA7712D70F62C0.png|625]] - $\large x\;+\;y$ 为两个**十进制整数 $x$ 和 $y$ 的算术加法结果**; - $\large x\;+^t_{w}\; y$ 为 $x$ 和 $y$ 在 **$w$ 位补码表示下的==补码加法==结果**。 > [!example] 补码加法计算示例 (8 位宽) > > **正数+正数**,可能产生**正溢出** > - $1\;[0000\;0001]+127\;[0111\;1111]=-128\;[1000\;0000]$ 正溢出 > - $127\;[0111\;1111]+127\;[0111\;1111]=-2\;[1111\;1100]$ 正溢出 > > **负数+负数**,可能产生**负溢出** > - $-127\;[1000\;0001]+(-127\;[1000\;0001])=2\;[0000\;0010]$ 负溢出 > - $-128\;[1000\;0000]+(-128\;[1000\;0000])=0\;[0000\;0000]$ 负溢出 > - $-1\;[1111\;1111]+(-128\;[1000\;0000])=127\;[0111\;1111]$ 负溢出 > > 正数+负数,不会产生溢出 <br> ### 补码减法 **==补码减法==**(给定位宽下):**转换为加法运算**, $X-Y => X+(-Y)$ ,**对 $X$ 和 $-Y$ 做补码加法**: $ [X-Y]_{补码}=[X]_{补码}+[-Y]_{补码} $ > [!NOTE] 补码表示下的加法逆元 $-Y$ > > 无论 $Y$ 是正数还是负数,$-Y$ 即是**对 $Y$ 的补码表示再求补码**——即**对 $Y$ 的补码==取反+1== 得到 $-Y$** > > 对于一个**负数 $-b$**($b$ 是正数),在算术逻辑上**减去 $-b$ 即 $-(-b)=b$** , > 在补码表示中**体现为==对 $-b$ 的补码表示取反加一==,得到其绝对值 $b$ 的二进制表示(也即 b 的补码表示)** > [!important] 补码取反 > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-69DC7C204208BA90B12A9050826749CE.png|625]] > - $-^t_{w} x$ 是对真值 $x$ **在 $w$ 位补码表示下取反**的结果 > - $-x$ 是对 $x$ **在十进制下的取反**,即**取相反数** > > [!example] 补码减法计算示例 (8 位宽) > > 非负数减正数,不会溢出 > > - $0\;[0000\;0000]-127\;[0111\;1111]=-127\;[1000\;0001]$ > > 非负数减负数,可能产生正溢出 > > - $0\;[0000\;0000]-(-128\;[1000\;0000])=0\;[0000\;0000]+(-(-128))\;[1000\;0000]=-128\;[1000\;0000]$ 正溢出 > - $1\;[0000\;0001]-(-128\;[1000\;0000])= 1\;[0000\;0001]+(-(-128))\;[1000\;0000]=-127\;[1000\;0001]$ 正溢出 > - $1\;[0000\;0001]-(-127\;[1000\;0001])=1\;[0000\;0001]+(-(-127))\;[0111\;1111]=-128\;[1000\;0000]$ 正溢出 > - $127\;[0111\;1111]-(-128\;[1000\;0000])=127\;[0111\;1111]+(-(-128)\;)[1000\;0000]=-1\;[1111\;1111]$ 正溢出 > > 负数减正数,可能产生负溢出 > > - $-1\;[1111\;1111]-127\;[0111\;1111]=-1\;[1111\; 1111]+(-127\;[1000\;0001])=-128\;[1000\;0000]$ 没有溢出 > - $-128\;[1000\;0000]-1\;[0000\;0001]=-128\;[1000\; 0000]+(-1\;[1111\;1111])=127\;[0111\;1111]$ 负溢出 > - $-127\;[1000\; 0001]-127\;[0111\; 1111]=-127\;[1000\;0001]+(-127\;[1000\;0001])=2\;[0000\;0010]$ 负溢出 > - $-128\;[1000\;0000]-127\;[0111\;1111]=-128\;[1000\;0000]+(-127\;[1000\;0001])=1\;[0000\;0001]$ 负溢出 > <br> ### 补码乘法 > ❓<font color="#c00000">位宽为 w 的补码乘法是如何计算的?在硬件层面、高级程序语言层面有何不同?</font> ^i6lffr 位宽为 w 的两个二进制数相乘,其**结果共需要 ==2w 位==来表示**。 **位宽 w 下的==有符号数乘法==**,**不能直接用两个数的补码做 "二进制乘法"**,因为 **w 与 2w 位宽下的==符号位==的位置不同**。 为此,需要先对两个有符号数进行 "**==符号扩展==**" => 均**扩展到 2w 位**,即将**符号位对齐到 2w 下最高位**, 再执行 "**二进制乘法运算**",最终 **==保留 2w 位运算结果==**。 如下图所示: ![[Excalidraw/Excalidraw-Solution/有符号整数的表示.excalidraw.md#^group=YW1tJjTXqyPzR4kWJvwND|766]] 由上图可知,**"==w 位补码表示的有符号数乘法==" 与 "==w 位无符号数乘法==",所得乘法结果==在 2w 位表示下可能不相同==(==低 w 位一定相同==,高 w 位可能不同)** > [!summary] > > - 在**硬件层面的机器指令**中会区分 "**有符号数乘法指令**" 与 "**无符号数乘法指令**",**硬件会==保留 2w 位的计算结果==在寄存器中** 。 > - **"==w 位补码表示的有符号数乘法=="** 与 "==**w 位无符号数乘法**==",所得乘法结果**在 2w 位表示下可能不相同**<br>(**==低 w 位一定相同==**,高 w 位可能不同) > > > > > - 在**高级程序语言层面**,通过将 "**2w 位的乘积结果==截断保留 w 位==**" 来得到**乘法运算结果**。 > - 在这种**截断方式下**,无论是**有符号数还是无符号数**,截断所得的 **==低 w 位==的位级表示都是相同的**。 > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-E19C101FFA8B8506F355439E07FACA85.png|454]] ^3v2uar > [!example] w 位有符号乘法与无符号乘法,**==完整 2w 位乘法结果的位模式==** 可能不同,但是==截断保留低 w 位所得的位模式一定相同==。 > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-34103AA3310110C31FBD5A4F76DE37AA.png|613]] > [!info] 两个分别为 n、m 位的乘数,其乘积结果至少为 n + m - 1 位,至多为 n + m 位。 > > ![[Excalidraw/Excalidraw-Solution/有符号整数的表示.excalidraw.md#^group=vozBH2n8|460]] > > #### 截断保留 w 位下,补码乘法的结果求值 **给定位宽 w 下,求补码乘法的运算结果**: ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-FF0C17445566631670804B9BC69F014A.png|604]] - $\large x\;\cdot\;y$ 为两个**十进制整数 $x$ 和 $y$ 的算术乘法结果**(可正可负) - $\operatorname{mod}\; 2^w$ 表示**模 $2^w$,正余数,范围为 $[0, 2^{w}-1]$;** - $U2T_{w}(\cdot)$ 是将**无符号整数对应的 $w$ 位二进制位模式作为补码**,**解释为补码数字** - $\large x\;*^t_{w}\; y$ 为 $x$ 和 $y$ 在 **$w$ 位补码表示下的==补码乘法==结果(截断保留低 2w 位)**。 > [!example] 补码乘法示例 (8 位宽) > > 正数乘法 > > - $2\;[0000\;0010] * 127\;[0111\;1111]=-2\;[1111\;1110]$ > - $3\;[0000\;0011] * 127\;[0111\;1111]=125\;[0111\;1101]$ > - $4\;[0000\;0100] * 127\;[0111\;1111]=-4\;[1111\;1100]$ > - $5\;[0000\;0101] * 127\;[0111\;1111]=123\;[0111\;1011]$ > - $6\;[0000\;0110] * 127\;[0111\;1111]=-6\;[1111\;1010]$ > - $7\;[0000\;0011] * 127\;[0111\;1111]=121\;[0111\;1001]$ > - $127\;[0111\;1111] * 127\;[0111\;1111]=1\;[0000\;0001]$ > > 负数乘法 > > - $-1\;[1111\;1111]*(-128\;[1000\;0000])=-128\;[1000\;0000]$ > - $-2\;[1111\;1110]*(-128\;[1000\;0000])=0\;[0000\;0000]$ > - $-3\;[1111\;1101]*(-128\;[1000\;0000])=-128\;[1000\;0000]$ > - $-4\;[1111\;1100]*(-128\;[1000\;0000])=0\;[0000\;0000]$ > - $-127\;[1111\;1100]*(-128\;[1000\;0000])=-128\;[1000\;0000]$ > - $-128\;[1000\;0000]*(-128\;[1000\;0000])=0\;[0000\;0000]$ > > 一正一负 > > - $2\;[0000\;0010]*(-128\;[1000\;0000])=0\;[0000\;0000]$ > - $3\;[0000\;0011]*(-128\;[1000\;0000])=-128\;[1000\;0000]$ > - $4\;[0000\;0100]*(-128\;[1000\;0000])=0\;[0000\;0000]$ > - $5\;[0000\;0101]*(-128\;[1000\;0000])=-128\;[1000\;0000]$ > - $6\;[0000\;0110]*(-128\;[1000\;0000])=-0\;[0000\;0000]$ > - $127\;[0111\;1111]*(-128\;[1000\;0000])=-128\;[1000\;0000]$ > - $127\;[0111\;1111] * (-2\;[1111\;1110])=2\;[0000\;0010]$ > - $127\;[0111\;1111] * (-3\;[1111\;1101])=-125\;[1000\;0011]$ > - $127\;[0111\;1111] * (-4\;[1111\;1100])=4[0000\;0100]$ > - $127\;[0111\;1111] * (-5\;[1111\;1011])=-123[1000\;0101]$ > - $127\;[0111\;1111] * (-6\;[1111\;1011])=6[0000\;0110]$ > - $127\;[0111\;1111] * (-7\;[1111\;1011])=-121\;[1000\;0111]$ > - $127\;[0111\;1111] * (-127\;[1000\;0001])=-1[1111\;1111]$ <br> ### 补码除法 **==补码除法==**(给定位宽下): - **只在一种情况下会产生溢出**:$-2^{w-1} / -1$ 时,结果会溢出为 $-2^{w-1}$。 > [!tip] 算术右移 `X>>i` 等价于**除以** $2^i$ **并向下取整(==向负无穷取整==)**,结果为 $\left\lfloor\frac{X}{\Large 2^i}\right\rfloor$。 > [!danger] C++中如果除数为 0,将会导致程序直接异常终止。 <br><br><br> ## 补码的溢出(Overflow) 在对有符号整数的补码下,溢出包括**正溢出**(Positive Overflow)和**负溢出**(Negative Overflow) - **正溢出**:计算结果超出数据类型 **==可表示的最大正值==** 时,发生**正溢出**,回绕得到一个**负数**; - **负溢出**:计算结果超出数据类型 **==可表示的最小负值==** 时,发生**负溢出**,回绕得到一个**正数**; 给定位宽的补码表示下,**加减乘除运算都可能导致溢出**。 > [!info] 补码的溢出 > > ![[_attachment/02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示.assets/IMG-有符号整数的表示-F97C15E0B914A9D7F1F44974C2C0013C.png|575]] > > 如上图所示(4 位宽),补码的 "**==溢出边界==**" 是在**给定位宽下补码可表示的 "最大值" 和 "最小值" 之间**, > 而不是在二进制位模式的最大位模式和最小位模式之间。 > > 例如,有符号数 $-1[1111] + 1[0001] = 0[0000]$ **单看二进制位模式(补码)实际上已经发生了回绕**,但并没有溢出,结果仍在 4 位补码可表示的有效范围内。 #### 补码加法的溢出判断 - **==异号==** 的两个数(一正一负)相加, **==不会发生溢出==** - **==同号==** 的两个数相加,结果为 **==异号==** 则表明发生溢出 - **两个正数相加**,若结果为**负数**,表明发生**正溢出**; - **两个负数相加**,若结果为**正数**,表明发生**负溢出**。 通过**观察==操作数和结果的符号位是否相同==即可判断是否溢出**,而不需要额外的溢出检测逻辑。 ```cpp bool signed_add_overflow (int x, int y) { // 溢出返回1, 未溢出返回0 int sum = x + y; int neg_over = x < 0 && y < 0 && sum > 0; int pos_over = x > 0 && y > 0 && sum < 0; return neg_over || pos_over; } ``` #### 补码减法的溢出判断 - **同号**的两个数相减,**不会发生溢出** - **正数**减去**负数**,若结果**小于被减数**,则表明发生**正溢出**,溢出得到负值; - **负数**减去**正数**,若结果**大于被减数**,则表明发生**负溢出**,溢出得到正值; ```cpp bool signed_subtract_overflow(int x, int y) { int res = a - b; return (a > 0 && b < 0 && res < a) || (a < 0 && b > 0 && res > a) } ``` > [!caution] **补码减法==不能复用补码加法的溢出判断逻辑==**。 > > 尽管减法都会转换为加法,但由于补码**表示范围非对称**,因此**补码减法不能复用补码加法的溢出判断逻辑**。 > > 例如,$0-(-128)$ 算术加法的预期结果是 128,但在 **8 位**下产生**正溢出得到 $-128$** : > > 计算机硬件对 $0-(-128)$ 的实际运算过程会转为 $0+(-(-128))$,**即对 $-128$ 的补码表示再取补码**,**得到 $(-(-128))$ 的补码表示**后**与 $0$ 的补码表示执行二进制加法**。 > 然而, 8 位补码表示下是没有正数 $128$ 的,==**对 $-128\;[1000\;0000]$ 再求补码得到的仍然是 $-128\;[1000\;0000]$ **==。 > 此时==**两个二进制位模式的符号位是异号的,但仍然发生了溢出**==,可见补码加法的溢出判断逻辑不适用。 #### 补码乘法的溢出判断 通过**除法**来判断补码乘法是否会溢出。 ```cpp bool signed_multiply_overflow(int x, int y) { int p = x * y; return x && p/x != y; // x不为0, 且p除以x不等于y, 则说明溢出. } ``` > [!example] 以 `x` 为 `INT_MIN`,`y` 为 `-1` 为例,此时 `p==INT_MIN`,`p/x==1`, `p/x!=y`,所以**可以判断溢出**。 > [!NOTE] 在硬件层面,对于 2w 乘法运算结果,编译器可通过判断高 w+1 位是否满足全 0 或全 1 来判断,**若满足则未溢出**。 > > 2w 位乘法结果中,**==高 w+1 位全 0 或者全 1==**,说明**乘法运算的结果仍然在 ==w 位有符号数的可表示范围==内**。 > #### 补码除法的溢出判断 - **$w$ 位补码表示**下,补码除法**只在一种情况下会产生溢出**:$-2^{w-1} / -1$ 时,结果会溢出为 $-2^{w-1}$。 #### C++中的溢出示例 ```cpp // 窄化转换-超出范围: 溢出 // long long类型或unsigned int类型的值2147483648转换为int类型. // 2147483648超出了int的表示范围(INT_MAX==214748364), 因此发生溢出. // 2147483648的二进制补码表示是0x80000000 // 位模式0x80000000不变, 但在int类型下被解释为-2147483648. long long lli {2147483648}; unsigned ui {2147483648}; assert((int) lli == -2147483648); assert((int) ui == -2147483648); int v = 2147483648; // long long类型的字面量 assert(v == -2147483648); ``` #### 溢出处理 - **编程中的处理**:在高级语言编程中,通常有特定的异常处理机制或状态标志来检测和处理溢出。 - **硬件中的处理**:许多 CPU 设计包括用于检测算术溢出的标志位,**如 x86 架构中的溢出标志(OF)**。 <br><br><br> # 关于计算机中硬件计算的说明 - 硬件执行 **加减运算** 时,不区分**有符号与无符号数**,均应用 **==相同的加减指令==**。 - 硬件执行 **==乘法==运算** 时: - ![[02-开发笔记/03-计算机基础/数据的编码表示/有符号整数的表示#^3v2uar]] <br><br> # 参考资料 # Footnotes [^1]: [南京大学《计算机系统基础(一):程序的表示、转换与链接》中国大学慕课,袁春风老师](https://www.icourse163.org/learn/NJU-1001625001?tid=1472100484#/learn/content?type=detail&id=1257458477&sm=1) [^2]: [补数 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E8%A1%A5%E6%95%B0)