到了2.1,很多同学才第一次意识到计算机组成原理不是“背”出来的,而是“算”出来的。408计组里,数制与编码这个知识点乍一看只是进制转换和几个机器数定义,但它几乎是后面所有计算的土壤:浮点数表示、Cache地址计算、指令编码、甚至ALU里补码运算,全部从这里长出来。这篇内容适合正在备考408、或者复习期末计组的人,尤其是那种“进制转换会一点、补码总搞混、海明码完全靠背”的同学。我会把每个考点拆开,尽量讲清楚“为什么”,再给你可以直接照做的做题套路。
1. 数制基础:为什么进制转换不能靠临时背表
1.1 任意进制转十进制:权展开是最稳的一条路
R进制转十进制,核心就一句话:按位权展开求和。这句话听起来很教科书,但很多人在做题时会犯一个低级错误——把二进制数里的“10”直接当十进制10来读。二进制只有0和1,位权是2的若干次方,从低位往高位依次是1、2、4、8……,小数点之后依次是1/2、1/4、1/8……。
举个直接能用上的例子:二进制1101.101转十进制,就是
1×2^3 + 1×2^2 + 0×2^1 + 1×2^0 + 1×2^(-1) + 0×2^(-2) + 1×2^(-3)
等于8+4+1+0.5+0.125 = 13.625。十六进制也一样,比如3F.C转十进制,3×16 + 15 + 12×16^(-1) = 48+15+0.75 = 63.75。做题的时候我习惯先写成权展开式,再逐项算,省得口算跳步出错。特别是小数部分,不要因为看着像“点几”就直接写个小数。
1.2 十进制转二进制:整数除基取余,小数乘基取整
这是另一个“背了无数遍但还是会错方向”的操作。整数部分用除2取余法,余数从下往上读;小数部分用乘2取整法,整数位从上往下读。我见过太多人在这一步把顺序写反,结果整个数字对不上。
以45.625为例。45除2的余数依次是:45÷2=22余1,22÷2=11余0,11÷2=5余1,5÷2=2余1,2÷2=1余0,1÷2=0余1。倒着读余数,得到101101。小数0.625乘2:0.625×2=1.25取整数1,0.25×2=0.5取0,0.5×2=1.0取1,顺读得到101。合起来就是101101.101。
需要注意,如果小数部分一直乘不到0,说明这个十进制小数在二进制里可能是无限循环小数。比如0.1这种非常常见的数,转成二进制就是0.0001100110011……,这正是浮点数精度误差的根源。考试里如果遇到“十进制小数能否精确表示”这类选择,答案往往是不能,得从二进制位数的有限性去想。
1.3 二、八、十六进制快速互转:三位一组、四位一组
这三个进制之间的转换,不需要回到十进制绕一圈。二进制转八进制,以小数点为界,整数部分从右往左每3位一组,小数部分从左往右每3位一组,不足补0;二进制转十六进制则按4位一组。
例如101101.101,转八进制:整数部分从右往左分成101和101,对应5和5;小数部分101对应5,所以结果是55.5。转十六进制:整数部分从右往左分成0010和1101,单独看是2和D;小数部分1010是A,最终得到2D.A。你可以反向验证一下,45.625确实等于八进制55.5,也等于十六进制2D.A。
实际做题时,我建议把十六进制作为中间桥梁。看到二进制长串,先写成十六进制缩短位数,再去做补码或者地址计算会舒服很多。反过来,遇到十六进制转二进制,每一位拆成4位二进制即可,比如0x2F就是0010 1111。
1.4 进制转换里的隐藏失分点
除了顺序问题,还有一个508考生常踩的坑是“负数的进制表示”。正数的二进制看着还算顺手,一旦遇到“-45的十六进制是多少”,很多人就开始懵。正确的处理路径是:先求出45的二进制,再按题目要求写成原码、反码或补码,最后从补码转成十六进制。不要试图直接“心算负数”,更不要在符号位上乱补1。
另一个失分点是位数不足时补0。比如要把十进制5写成8位补码,不能直接写101,而是要先写00000101,再求反加一得到11111011。如果一开始就写反码,补出来的高位全是1,结果一定错。
2. 机器数编码:原码、反码、补码、移码的底层逻辑
2.1 原码和反码为什么只是“过渡产物”
原码最符合人的直觉:最高位是符号位,0正1负,其余位是绝对值。但问题是,原码在做减法时非常麻烦,要单独判断符号位,还可能出现两套0:+0和-0。比如4位原码里,0000是+0,1000是-0,这显然不是好的设计。
反码是在原码基础上,把负数的数值位全部取反。它解决了“求反”的问题,但+0和-0依然存在:0000对应+0,1111对应-0。所以反码在计算机里更多是作为补码的中间步骤存在,本身很少直接参与运算。
补码的出现,核心目的是把减法统一成加法。一个非常直观的理解是“模运算”:在一个字长限定的机器里,加满之后会溢出归零,这就像钟表走到12点回到0点一样。减去一个数,等价于加上它的模补数。补码的定义是,负数补码等于其绝对值对应的正数取反加1。因为多了这个“末位加1”,4位补码里1000不再表示-0,而是表示-8。补码范围因此比原码多了一个最小负数,这也是408常考的一句话:n位补码能表示的范围是-2^(n-1)到2^(n-1)-1。
2.2 补码运算与溢出判断:符号位进位不是错误
补码加法有一个反直觉的点:符号位产生的最高位进位可以丢弃。比如4位补码里算5+(-3),0101 + 1101 = 10010,把最高位的进位丢掉,留下0010,结果就是2,完全正确。这也是为什么补码适合硬件实现:同一套加法电路,加法和减法都能做。
但有进位能丢,不代表任何溢出都能丢。溢出判断是408选择题里的高频陷阱。最直观的方法是看结果符号位:两个正数相加,如果得到负数,说明溢出;两个负数相加,如果得到正数,也说明溢出。比如4位补码范围是-8到7,6+3等于1001,在4位补码里是-7,这显然不对,说明溢出。另一个更底层的判据是“最高位进位和次高位进位是否相同”,如果不同就溢出。这个原理在计组和组成原理里都可能考到,建议当成结论记住。
2.3 移码:浮点数阶码的“伪装”
移码从补码变来也很简单:补码符号位取反就是移码。移码之所以存在,主要是为了让浮点数的阶码能够方便比较大小。浮点数表示里,阶码通常用移码或偏置码,而不用原码补码,因为移码能保证“阶码大的数值也大”,方便排序和比较。
使用移码时一定要看清字长和偏置量。4位移码(偏置8)里1000表示0,0000表示-8,1111表示7。而在IEEE 754单精度浮点里,阶码是8位,偏置127,全0和全1还有特殊含义。408常把移码和浮点数放在一起考,单独出“给真值求移码”的概率不高,但你必须能在浮点数题目里快速反应过来。
| 编码 | 4位范围 | 0的表示 | 核心特点 |
|---|---|---|---|
| 原码 | -7 ~ 7 | 0000 / 1000 | 直观,加减复杂 |
| 反码 | -7 ~ 7 | 0000 / 1111 | 负数取反,0不唯一 |
| 补码 | -8 ~ 7 | 0000 | 减法变加法,硬件友好 |
| 移码 | -8 ~ 7 | 1000 | 补码符号位取反,偏置码 |
2.4 机器数真题里的经典陷阱
刷历年408真题时,你会发现机器数不太会“裸考”,而是藏在一个看似简单的选择题里。最常见的是给一个十六进制补码,比如0xE0,问你它的真值是多少。这一步必须先展开成二进制1110 0000,看到符号位是1,于是按补码解码:先减1得1101 1111,再按位取反得0010 0000,也就是-32。如果直接用“取反加一”从补码求原码也行,注意方向别反。
另一个高频点是符号扩展。8位补码11010111扩展成16位,不能直接在高位补0,而要按符号位补成1111111111010111。多数学生第一次都会在这里丢分。还有就是C语言里char和unsigned char的类型转换,在408默认的补码机模型下,char x = 0x80会被当作-128处理,一旦理解成128就会错。
3. 字符与数值编码:ASCII、BCD、Unicode的基本盘
3.1 ASCII码:不需要全背,但关键数字必须烂熟
ASCII属于字符编码,在计组课里不会展开太多,但408选择题偶尔会用大小写差值、数字字符差值来考你字符比较或转换。核心就记三组:'0'的ASCII码是48,十六进制0x30;'A'是65,0x41;'a'是97,0x61。大写和小写之间差32,也就是二进制里的0x20。
有了这三组数字,很多题目就能推。比如“把数字字符转成对应整数值”,'9'-'0'=9;判断一个字符是大写字母,只要看它是否在'A'到'Z'之间。考试不会让你背完整个ASCII表,但会让你根据差值做判断。还有一些常见控制字符也值得记,回车'\r'是13,换行'\n'是10,在文件读取和串口通信相关的题目里会用上。
3.2 BCD编码:8421码不是二进制数,是“十进制伪装”
BCD码用4位二进制表示一位十进制数字。比如45的BCD编码不是101101,而是0100 0101,分别对应十位的4和个位的5。它的好处是十进制显示和输入方便,坏处是运算时需要修正,因为4位二进制虽然能表示0到15,但BCD只用0到9。
BCD加法修正规则是:如果每一位的和大于9,或者产生了向高位的进位,就需要加6(即0110)修正。举个例子,8+5用BCD算,1000 + 0101 = 1101,1101超过了9,于是再加0110,得到0001 0011,也就是十位进位1、个位3,读作13。很多同学不理解为什么要加6,其实是因为BCD跳过了1010到1111这6个非法编码,从10到15需要人为“补”回6个值,才能正确产生进位。
3.3 汉字编码与Unicode:搞清楚字符集和编码方案的区别
Unicode和UTF-8这两层概念经常被弄混。Unicode是字符集,给每个字符一个码位,汉字“中”的码位是U+4E2D;UTF-8是把这个码位转换成字节流的编码方案,在UTF-8里“中”被编码为E4 B8 AD三个字节。字符集管“映射”,编码方案管“存储和传输”。
408计组大纲里不会直接让你背UTF-8表,但如果你要做指令编码相关的题,这种“字符对应到二进制”的思维是一致的。我更推荐把它当成一种课外背景来理解,不必深挖。真正的重点还是在机器数和校验码上,字符编码最多是一道送分选择题。
4. 校验码:不是背公式,而是要会手算
4.1 奇偶校验:最便宜,但只能“检奇位错”
奇偶校验的原理是在数据后面加一位校验位,让整个码字中1的个数为奇数(奇校验)或偶数(偶校验)。接收方再统计1的个数,判断是否被破坏。它虽然简单,但只能检测奇数个位错误,如果恰好两位、四位出错,1的个数奇偶性不变,错误就漏掉了。
真题里奇偶校验一般不单独考,而是作为海明码和CRC的背景知识出现。你只需要记住码距概念:两个合法码字之间不同的二进制位数,叫码距。一个编码方案的码距是2时,能检出一位错;码距是3时,可以纠一位错或检两位错。奇偶校验的码距就是2。
4.2 海明码:分组校验,目标是把“出错位置”算出来
海明码是想办法给数据增加若干校验位,让校验结果不仅知道“有没有错”,还能知道“错在哪一位”。它需要满足海明不等式:2^k ≥ n+k+1,其中n是数据位,k是校验位。4位数据需要3位校验位,8位数据需要4位校验位。
构造规则是:校验位放在2的幂次位置,也就是第1、2、4、8位。第i个校验位负责校验那些二进制编号中第i位为1的数据位。举个一定能算的例子,数据D3D2D1D0=1010,按位置排成b1=P1、b2=P2、b3=D0、b4=P4、b5=D1、b6=D2、b7=D3,也就是b3=0、b5=1、b6=0、b7=1。用偶校验算:P1需要让b1、b3、b5、b7中1的个数为偶数,0+1+1已经有偶数个1,所以P1=0;P2管b2、b3、b6、b7,1的个数为奇数,所以P2=1;P4管b4、b5、b6、b7,也是偶数个1,所以P4=0。最终海明码是0100101,按b1到b7排列。
如果接收到的码字里第5位发生反转,b5从1变成0。接收方重新按P1、P2、P4分组做偶校验,得到三组结果S4S2S1=101,也就是5,直接指出错误发生在第5位,然后取反纠错。这就是海明码的经典做题路径:先排位置,再算校验位,最后综合校验结果定位。
4.3 CRC码:模2除法,重点会算生成多项式
CRC的核心是把数据看作二进制多项式,用生成多项式去模2除。模2除法就是异或运算,不进位、不借位。题目里常用的生成多项式是x^3+x+1,对应二进制1011,最高次是3,所以要在信息位后补3个0。
以信息位M=101001为例,先补0得到101001000。用1011逐位去除:每一步盯住当前参与运算的最高位,是1就异或1011,是0就往下移一位。最终算出的余数是100,所以发送码字是101001加上余数100,得到101001100。接收方收到后,用同样的生成多项式1011去除,如果余数为0,则认为传输没有出错。
CRC最大的优势是检错能力强,尤其能检测突发错误,所以它在以太网帧里大量使用。它和奇偶校验不一样,通常只用来检错,不纠错。考场上最稳妥的做法是像上面这样手写一遍模2除法,不要在草稿纸上心算异或。
| 校验码 | 冗余位数 | 主要能力 | 典型场景 |
|---|---|---|---|
| 奇偶校验 | 1位 | 检奇数位错 | 串口通信 |
| 海明码 | 约log(n) | 纠1位错,检2位错 | 内存ECC |
| CRC | r位 | 检突发错,不纠错 | 网络帧校验 |
5. 真题规律与复习策略:把2.1变成稳定得分点
5.1 选择题里最常见的三种出法
我在刷历年408真题时发现,直接考数制与编码的选择题并不难,但出题角度很固定。第一种是给一个十六进制补码求真值,或者反过来给真值求补码,考察机器数转换。第二种是给一个二进制或十六进制数,让你比较大小,本质上还是要先统一到补码或移码视角。第三种是给海明码或CRC的计算过程,要求你判断错误位或选择正确码字。
应对这三种题,只需要一个习惯:拿到任何数值,先在草稿纸上写成等长的二进制位串。不要跳步,不要只在脑子里算。很多时候错误不是因为不懂,而是因为十六进制和十进制混在一起把人绕晕了。
5.2 数制与编码如何串起整个计组
2.1的知识点和后面章节是强绑定。浮点数表示里的阶码用移码,尾数用原码或补码;定点补码运算在ALU章节会重复出现;Cache地址划分时要用十六进制算索引和标记;指令编码本质上就是操作码和地址码的二进制分配。
所以如果你只把这章当独立小节复习,会非常可惜。我建议每学一个后面的大块内容,都回头看一眼2.1:看到浮点数就找阶码和移码,看到Cache就找地址位段,看到指令就找操作码字段。这样学一遍,等于把数制编码复习了三遍,记忆会牢固很多。
5.3 复习误区与时间安排
误区一是浪费时间背“65536是2的几次方”这种表,结论当然要记,但只要在题目里遇到几次自然就记住了,不需要专门背。误区二是只做计算题,不练概念辨析题。“原码反码补码的区别”“移码为什么存在”“海明码码距为什么是3”这类概念题才是容易丢分的地方。
时间安排上,2.1本身内容不多,基础好的同学两天足够。第一天把进制转换和机器数手写过一遍,第二天集中做王道书对应选择题,以及历年408真题里所有涉及编码的题。我复习的时候没有急着看强化课,而是先自己手算了三组数:-128的补码、0.1的二进制、D3D2D1D0=1010的海明码。这三组数通了,这章的心也就定了。
如果你正在跟王道强化课,不用担心这章内容少,强化老师会把补码运算、海明码、CRC串在后面的浮点数和网络章节里反复提。但前提是基础阶段你自己已经能独立算一遍,否则课上看起来全是“听懂了”,一动手还是卡壳。
最后分享一点个人体会。我在这节上栽过两次:第一次是十进制小数转二进制,乘2的顺序读反了;第二次是海明码,把校验位位置和分组关系背混了。后来我强迫自己“每道题都画位置编号”,海明码再也没错过。408的容错率不在于你懂多少知识,而在于这种相对固定的送分题能不能稳定拿分。把2.1的数制与编码当成一道计算题而不是背诵题,你会发现它其实是整张卷子里最友好的部分。