十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

计算机组成原理:原码、反码、补码、移码及溢出判断详解

计算机组成原理:原码、反码、补码、移码及溢出判断详解 复习计算机组成原理的时候很多同学最先遇到的拦路虎并不是 Cache 或流水线而是第一章“数据的表示与运算”里的原码、反码、补码、移码。网课听的时候觉得思路清楚无非是“负数取反加一”可真到做题符号位、模、溢出判断、无符号数和有符号数混在一起就特别容易翻车。尤其是 408 统考和各类院校自命题里“移码补码经典考法”几乎是每年都会出现的题型分值虽然不大却能在选择题、填空题甚至大题第一问里连续设卡。这篇文章就把这一块内容完整梳理一遍。从机器数和真值的关系开始再到原码、反码、补码、移码的定义与转换最后用大量手算例题把补码加减、溢出判断、无符号数的补码这些高频考法逐一拆开。所有结论都会给出推导思路部分示例会配上 Python 和 C 语言代码验证方便你在复习时自己动手跑一遍。无论你是在准备 408还是正在学《计算机组成原理》期末考这篇内容都可以作为一份完整的复习笔记使用。1. 从真值到机器数补码和移码到底在解决什么问题1.1 真值与机器数的区别在计算机里所有数据最终都要变成 0 和 1 组成的二进制串。为了区分概念教材里通常把带正负号的十进制数、二进制数称为“真值”例如5、-5、-27而把它们存储在计算机中的 01 编码称为“机器数”。举个例子真值-5如果写成机器数可以是10000101也可以是11111011具体是哪个取决于采用什么编码规则。这些规则就是我们常说的原码、反码、补码、移码。这里需要明确一点真值是给人看、给数学计算用的机器数是给硬件存、给硬件算用的。所以“计算机怎么表示负数”本质上是一个编码设计问题。设计编码时需要考虑三个现实需求符号位如何处理加减法能不能统一成加法0 的表示是否唯一你会发现原码把“直观”做到了极致但运算麻烦补码把“表达范围”和“运算统一”做到了极致但初学时不够直观移码则专门服务于“比较大小”和“浮点数阶码”场景。理解了这些需求你才能从根上记住它们各自的特性而不是靠死记硬背。1.2 引入多种编码的原因为什么不能只用一种编码原因很简单没有一种编码能在所有场景下同时满足直观、运算简单、硬件实现成本低这三个要求。我们看一个最典型的矛盾。如果用原码表示负数符号位必须单独处理加法器做A B时先判断符号位是否相同再决定是相加还是相减最后还要确定结果的符号。硬件电路会变得很复杂。而补码的核心思想是“符号位也可以参与运算减法可以变成加法”这样 CPU 里的加法器就能通吃加减法。再看移码。移码本身并不用于普通加减运算它最大的特点是移码的大小顺序和真值的大小顺序完全一致。这意味着两个移码可以直接按无符号数比较大小非常适合浮点数中阶码的比较和排序。因此不同编码服务于不同需求考试里考察“为什么用补码”“为什么移码能直接比大小”本质上就是在考这些设计动机。1.3 模与同余补码的数学基础补码听起来很玄其实它的数学基础是“模”和“同余”。在 n 位二进制数中所有运算都对2^n取模2^n就被称为模。以 8 位二进制数为例模是2^8 256。对一个数取模相当于把它在数轴上按周期 256 循环折叠。于是你可以发现一个非常特殊的现象-1 ≡ 255 (mod 256) -2 ≡ 254 (mod 256) -3 ≡ 253 (mod 256)这里“≡”表示同余。也就是说在 8 位环境下-1和255在模 256 的意义上是等价的。那么问题来了计算机不擅长表示负数但很擅长表示无符号数255于是我们干脆用11111111来表示-1的补码。这一步是整个补码体系的核心。为什么补码的加法规则是“按 2^n 取模后相加”因为[X Y]补 (X Y) mod 2^n (X mod 2^n Y mod 2^n) mod 2^n右边的X mod 2^n正是 X 的补码Y mod 2^n是 Y 的补码。所以补码相加就是两个补码直接相加再取模。理解了模你就能解释很多“奇怪”的结论。比如 8 位补码10000000表示-128为什么比其他负数多一个因为-128的补码是256 - 128 128对应二进制10000000它没有对应的原码形态。这个数在原码和反码中都表示不出来这也是补码表示范围比原码、反码多一个最小负数的原因。2. 原码、反码、补码定义、转换与易错点2.1 原码最直观的表示方式原码的规则是最容易理解的最高位是符号位0 表示正数1 表示负数其余位是数值的绝对值。以 8 位机器数为例5的二进制绝对值是0000101加上符号位后得到00000101。-5的符号位改成 1得到10000101。0是00000000-0是10000000。原码有两个很明显的问题第一个问题是 0 的表示不唯一。0和-0是两个不同的机器数这会导致硬件判断“结果是否为 0”时变得很麻烦。第二个问题是加减运算复杂。两个异号数相加不能直接用符号位相加而是要先比绝对值大小再做绝对值减法最后决定符号硬件实现成本很高。原码的实际应用场景并不算多常见于浮点数尾数的表示中因为尾数需要保留“直观的符号位”。考试里对原码的考察主要集中在“给出真值求原码”“给出原码求真值”以及“原码表示范围”这些基本操作上。8 位原码的表示范围是-(2^7 - 1) ~ (2^7 - 1)也就是-127 ~ 127。2.2 反码原码到补码的中间站反码的规则是正数的反码与原码相同。负数的反码是原码符号位不变数值位按位取反。继续以 8 位机器数为例5原码00000101反码还是00000101。-5原码10000101数值位0000101按位取反得到1111010所以反码是11111010。反码同样存在 0 不唯一的问题0是00000000-0是11111111。在实际计算机中反码很少作为最终存储形式使用它更像一个“过渡产物”。因为补码的求法是“负数反码加 1”所以反码是理解补码的中间站。考试里对反码的考察往往也是结合补码一起考比如“已知某负数的反码求该数的补码”。2.3 补码计算机内部真正使用的编码补码的规则是正数的补码与原码、反码相同。负数的补码是反码末位加 1也就是“原码符号位不变数值位取反加 1”。以 8 位机器数为例-5原码10000101数值位取反得到11111010再加 1 得到11111011。-128的补码很有代表性。它是10000000因为-128没有 8 位原码只能通过补码形式2^8 - 128 128得到。补码的最大优点是 0 的表示唯一。8 位补码中00000000表示 010000000不再表示-0而是表示-128。所以 n 位补码的表示范围是-2^(n-1) ~ (2^(n-1) - 1)8 位补码范围就是-128 ~ 127。从运算角度看补码最重要的性质是[X]补 [Y]补 [X Y]补 (mod 2^n)这意味着 CPU 不需要单独的减法器把减法转成补码加法即可。这也是为什么计算机内部普遍使用补码存储有符号整数。2.4 三码对照与快速换算口诀为了让你更直观地感受三种编码的差异下面给出一张 8 位机器数对照表列出部分关键值真值原码反码补码1270111111101111111011111111000000010000000100000001000000000 / 1000000000000000 / 1111111100000000-1100000011111111011111111-5100001011111101011111011-127111111111000000010000001-128无法表示无法表示10000000这张表是复习时最好的记忆锚点。你可以把表里的每个数手写推一遍比单纯背口诀有用得多。这里再总结几个考试常用的口算法则正数的原码、反码、补码完全一样不需要做任何变换。负数求补码符号位不变数值位取反末位加 1。你可以在做题时把符号位先“隔离”出来只对数值位操作这样能避免把符号位一起取反的低级错误。负数补码求原码对补码再求一次补也就是符号位不变数值位取反加 1。因为“取反加 1”互为逆运算。快速手算技巧对负数补码从右往左找到第一个 1这个 1 以及它右边的 0 保持不变左边的数值位全部取反得到的就是原码的数值部分。符号位保持不变。举个例子补码11110100求原码。符号位是 1说明是负数。数值位是1110100从右往左看位 2 是第一个 1所以位 2 和它右边的两个 0 保留位 3 到最高数值位1110取反为0001组合得到0001100也就是 12。所以真值是-12。3. 移码补码的镜像表示3.1 移码的定义与偏置值移码又叫增码、偏置码它的核心思想是把整个真值数轴整体平移一段距离让负数也变成无符号数。在 n 位机器数中标准移码的定义是[x]移 x 2^(n-1)这里2^(n-1)称为偏置值。对于 8 位机器数偏置值是128对于 4 位机器数偏置值是8。按照这个定义真值-128移码是-128 128 0即00000000。真值0移码是128即10000000。真值127移码是127 128 255即11111111。可以看到移码把原来的-128 ~ 127映射到了0 ~ 255映射后的结果正好可以用无符号数表示。你还需要掌握一个很重要的关系在标准移码中移码和补码之间只差一个符号位取反。例如真值-1288 位补码是10000000符号位取反后得到00000000确实和移码一致。再例如真值-1补码是11111111符号位取反后得到01111111按定义-1 128 127也就是01111111完全一致。这个结论可以帮你快速在补码和移码之间切换。3.2 移码为什么适合比较大小比较两个有符号数大小时如果直接看原码或补码会有一个麻烦补码按无符号数比较并不总是和真值大小一致比如-1的补码是11111111-2的补码是11111110按无符号数看11111111 11111110对应真值-1 -2这里恰好一致但换个场景就容易出错。比如-128的补码是10000000127的补码是01111111按无符号数看10000000 01111111于是得到-128 127这显然是错的。移码解决了这个问题。因为移码是整体平移得到的结果所以它保持真值的大小顺序不变。两个真值x y它们的移码一定满足[x]移 [y]移而且可以直接按无符号数比较。浮点数比较阶码大小时正是利用了移码这个性质。两个不同指数的浮点数指数大的数值一定更大而指数用移码存储后可以直接比较这大大简化了浮点数的排序和比较逻辑。3.3 浮点数阶码中的偏置值考法有一个特别容易踩的坑浮点数 IEEE 754 标准中的阶码虽然本质上是“移码思想”但偏置值并不总等于2^(n-1)。以单精度浮点数为例阶码占 8 位偏置值是127也就是2^7 - 1而不是标准移码中的128。因此阶码存储值 真值指数 127比如真实指数是-2存储值就是-2 127 125二进制是01111101。如果此时误用标准移码公式就会算出126答案就错了。所以你做题时要区分两种场景题目明确说“用移码表示”偏置值通常取2^(n-1)。题目涉及 IEEE 754 浮点数阶码偏置值要根据标准来单精度是 127双精度是 1023。这类考法在 408 真题和期末题里都出现过。看到浮点数阶码时第一反应不是套移码公式而是先确认标准规定的偏置值。4. 补码加减运算与溢出判断4.1 补码加减法把减法变成加法补码加减运算是计组考试中最高频的计算题。先看基本规则。补码加法[X Y]补 [X]补 [Y]补 (mod 2^n)补码减法[X - Y]补 [X]补 [-Y]补也就是说减法可以转换成“加上减数相反数的补码”。而求[-Y]补的方法就是对[Y]补连同符号位一起取反末位加 1。请注意这里和“负数求补码”的区别已知一个正数的补码要求它的相反数的补码是连同符号位一起取反加 1。举个例子8 位补码中[5]补 00000101 [-5]补 11111010 1 11111011再看一个减法实例。计算5 - 3[5]补 00000101 [-3]补 11111101 相加 00000101 11111101 1 00000010最高位进位 1 直接丢弃保留 8 位结果是00000010即2。可以看到减法确实变成了加法。手算补码加减法时建议写清楚进位过程尤其是最高位的进位要单独标出来因为后面判断溢出时用得上。4.2 溢出判断的三种实用方法补码加减法的难点在于溢出判断。当运算结果超出 n 位补码的表示范围时就发生了溢出。注意溢出和进位是两个不同的概念进位是指最高位产生了向更高位的进位溢出是指结果真值已经超出了表示范围。两者可能同时发生也可能单独发生。下面介绍三种最常用的溢出判断方法。方法一根据符号位判断两个正数相加结果却变成了负数说明正溢。两个负数相加结果却变成了正数说明负溢。一正一负相加由于结果绝对值不会超过其中较大的数所以一定不会溢出。例如 8 位补码中01111111 00000001 10000000两个正数相加得到负数-128显然溢出10000000 11111111 01111111两个负数相加得到正数127显然也溢出。方法二双符号位判断双符号位也叫变形补码。运算时用两个符号位参与计算保留两位结果。两位符号位同步为 00 表示正数正常11 表示负数正常如果出现 01表示正溢出如果出现 10表示负溢出。以上面的例子验证。8 位补码01111111写成双符号位形式是00 111111100000001是00 0000001。相加00 1111111 00 0000001 01 0000000结果符号位是01正溢出。再看-128 (-1)10000000 - 11 0000000 11111111 - 11 1111111 相加 11 0000000 11 1111111 1 10 1111111丢弃最高位进位后保留双符号位10负溢出。双符号位法在手工卷面答题时非常直观老师也容易给分。方法三根据最高位进位和符号位进位判断设 C1 是最高数值位向符号位产生的进位C2 是符号位产生的进位。如果C1 ⊕ C2 1则说明溢出。还是用127 1验证。01111111 00000001低 7 位运算1111111 0000001产生向符号位的进位所以 C1 1符号位0 0 进位1没有产生向更高位的进位所以 C2 0。1 ⊕ 0 1溢出。再验证-128 (-1)。10000000 11111111低 7 位运算过程中最高数值位0 1 低位进位0 1没有向符号位进位所以 C1 0符号位1 1 0 10产生进位 C2 1。0 ⊕ 1 1溢出。三种方法本质上是一样的考试时你可以选择最顺手的。我个人的建议是卷面上用双符号位法思路清楚且不容易被扣过程分检查时再用符号位法快速复核一遍。4.3 CF、OF、SF、ZF四个标志位不能混很多同学学到标志位时容易把进位标志 CF 和溢出标志 OF 混为一谈。这里把它们的区别彻底讲清楚。CFCarry Flag进位/借位标志反映无符号数运算结果是否超出了无符号数表示范围。它等于最高位进位 C2。OFOverflow Flag溢出标志反映有符号数运算结果是否超出了补码表示范围。它等于C1 ⊕ C2。SFSign Flag符号标志反映运算结果最高位即结果的正负。ZFZero Flag零标志反映运算结果是否为 0。最经典的例子还是 8 位补码01111111 00000001。运算结果截断为10000000最高位进位 C2 0所以 CF 0但C1 ⊕ C2 1所以 OF 1。这说明无符号数角度没有溢出但补码有符号数角度溢出。另一个经典例子是 8 位无符号数255 1。无符号11111111 00000001 1 00000000最高位进位为 1所以 CF 1但从补码角度-1 1 0结果没有溢出OF 0。这里的关键认知是同一组二进制串既可以被解释成无符号数也可以被解释成有符号补码数所以 CF 和 OF 要分别考察。考试选择题常问“计算后 CF 和 OF 分别是多少”你必须先区分题目是在讨论无符号数还是有符号数。5. 无符号数的补码问题5.1 无符号数没有补码但有模运算严格来说无符号数没有符号位所以不存在“原码、反码、补码”这种针对符号的编码方案。但在计算机底层无符号数的加减同样遵循模运算规则。比如 8 位无符号数0 - 1 ?从数学上讲应该是-1但无符号数范围是0 ~ 255无法表示负数。于是按照模 256 运算0 - 1 -1 mod 256 255在 C 语言中这被称为“回绕”wrap around。你可以写一个简单程序验证#include stdio.h int main() { unsigned char a 0; a a - 1; printf(0 - 1 %u\n, a); // 输出 255 unsigned char b 255; b b 1; printf(255 1 %u\n, b); // 输出 0 return 0; }运行结果0 - 1 255 255 1 0这就是“无符号数的补码”相关考法的本质无符号数运算使用模2^n当你计算A - B时实际上执行的是A (2^n - B)而2^n - B在数值上正好等于-B的补码表示。所以从存储形式上无符号数的减法也借助补码完成了。5.2 同一个二进制串的两种解释这是考研选择题里非常经典的设问方式给定一个 8 位二进制串问按无符号数解释是多少按补码解释又是多少。以10000000为例按无符号数解释就是128。按补码解释符号位为 1数值位全 0表示-128。同样一个10000000无符号和有符号的含义完全不同。再比如11111111按无符号数解释是255按补码解释是-1。01111111按无符号数解释是127按补码解释也是127。所以判断一个二进制串代表什么值前提是先明确“解释方式”。很多题目看起来是进制转换实际上考察的是这个辨析能力。Python 里可以用按位与0xFF快速查看 8 位补码形态def to_8bit(x: int) - str: if not -128 x 127: raise ValueError(超出8位有符号数范围) # 负数补码等价于对 256 取模 return format(x 0xFF, 08b) for n in [-128, -1, 0, 1, 127]: print(f{n:4} - {to_8bit(n)})输出-128 - 10000000 -1 - 11111111 0 - 00000000 1 - 00000001 127 - 01111111这段代码也验证了补码与模运算的关系负数x的 8 位补码本质上就是x 0xFF即x对2^8取模后的结果。5.3 代码里的真实坑有符号和无符号混用在 C/C 中有符号数和无符号数混用在表达式里时会发生隐式类型转换。转换规则是有符号数会被转换为无符号数然后按无符号数规则参与运算。这是很多 bug 的来源也是面试和考试都爱考察的场景。看下面一段代码#include stdio.h int main() { signed char a -1; unsigned char b (unsigned char)a; printf(a %d\n, a); // -1 printf(b %u\n, b); // 255 int x -1; unsigned int y 1; // 有符号 x 先转成无符号数再与 y 相加 if (x y 0) { printf(x y 0\n); } else { printf(x y 0\n); } return 0; }在 32 位 int 环境下x y的结果看起来是什么因为x -1转为无符号数是4294967295加上1后对2^32取模得到0所以判断分支会走else输出x y 0。这和“直觉”完全不同。在复习时这个例子能帮你理解“无符号数的补码”考法的现实意义理解补码和模运算不仅是为了做对卷面题更是为了在真实工程里避开类型转换带来的隐蔽 bug。6. 经典考法拆解按题型练一遍这一节把常见的“移码补码经典考法”整理成具体题型。每个题型都会给出完整手算过程建议你拿出草稿纸跟着算一遍。6.1 题型一已知补码求真值这是最基础的题型几乎逢考必出。例 18 位补码11110100求真值。符号位为 1是负数。要求真值需要从补码还原原码也就是对补码进行一次“取反加 1”补码 11110100 符号位不变数值位取反10001011 数值位加 110001100所以原码是10001100真值为-12。再用快速法复核一下从右往左找第一个 111110100从低位开始是 0、0、1找到第一个 1 后这个 1 和右边的两个 0 保留左边数值位1110取反为0001得到数值部分0001100即 12。符号位为 1所以真值是-12。例 28 位补码10000000求真值。符号位为 1数值位全 0。这是一种特殊情况表示-128。不能用“取反加 1”得到原码因为-128没有 8 位原码形式。考试里直接记忆结论即可10000000的补码对应-128。6.2 题型二已知真值求四种机器数例已知真值x -27用 8 位机器数表示求[x]原、[x]反、[x]补、[x]移。步骤拆分如下第一步写出27的二进制数。27 16 8 2 1 11011补齐到 7 位数值位为0011011。第二步填符号位得到原码[x]原 10011011第三步符号位不变数值位取反得到反码[x]反 11100100第四步反码末位加 1得到补码[x]补 11100101第五步按标准移码偏置2^7 128计算移码。可以直接用补码符号位取反[x]移 01100101也可以用定义验证-27 128 101二进制正好是01100101。这道题考察的是四种编码的相互转换特别需要注意移码和补码的关系。如果题目没有说明“标准移码”默认偏置值是2^(n-1)如果涉及浮点数阶码则要单独按 IEEE 754 规则处理。6.3 题型三补码加减与溢出判断综合例 18 位补码计算127 1并判断是否溢出。01111111 00000001 10000000结果按补码解释是-128而127 1的正确结果是128已经超出 8 位补码范围-128 ~ 127所以正溢出。用双符号位验证00 1111111 00 0000001 01 0000000符号位出现01正溢出。用进位异或验证最高数值位进位 C1 1符号位进位 C2 01 ⊕ 0 1溢出。例 28 位补码计算-128 (-1)并判断是否溢出。10000000 11111111 1 01111111丢弃最高位进位结果保留 8 位是01111111按补码解释是127而正确结果是-129所以负溢出。用双符号位验证11 0000000 11 1111111 1 10 1111111保留两位符号位是10负溢出。用进位异或验证C1 0C2 10 ⊕ 1 1溢出。这个题型的关键是不管结果看起来像什么数字先判断两个加数的符号。同号相加结果符号变了基本就是溢出。6.4 题型四移码比较大小与浮点阶码例 1用 4 位标准移码比较-3和2的大小。4 位标准移码偏置值是2^3 8。[-3]移 -3 8 5 0101 [2]移 2 8 10 1010移码0101按无符号数小于1010所以-3 2和真值大小顺序一致。例 2单精度浮点数阶码字段为10000001求真实指数。单精度阶码 8 位偏置 127。先把10000001转成十进制129。真实指数为129 - 127 2所以该浮点数的指数是
返回列表