共计 3576 个字符,预计需要花费 9 分钟才能阅读完成。
最近学习计算机组成原理时,结合视频和个人理解做的一些笔记。可能文中有错误或者描述不恰当的地方,欢迎评论区指出。
1. 定点数的表示
1.1 无符号数
- 定义:无符号数就是没有体现正负号的数(这意味着所有的无符号数实际上都是正数),整个机器字长的全部二进制位均为数值位,没有符号位。以
108D
为例,它对应的二进制数是1101100
,这实际上也就是它的无符号数,可以看到所有的位都是数值位。 - 表示范围:以八位二进制数为准,范围就是
00000000
到11111111
,也就是 0 到 255
1.2 有符号数
- 定义:有符号数就是有体现正负号的数,整个机器字长的全部二进制位中,最高位作为符号位,0 表示正数,1 表示负数,其余位则是数值位。依然以
108D
为例,它对应的二进制数是1101100
,而对应的有符号数则要在最前面加上符号位 0,即它的有符号数是01101100
. - 表示范围:以八位二进制数为准,范围应该是从负数到正数,即从
11111111
到01111111
,也就是 -127 到 127
1.2.1. 真值和机器数
- 真值:就是带有正负号的实际十进制数,比如上面例子中,
+108D
就是真值 - 机器数:机器数就是一个数在计算机中的二进制表示形式,注意 机器数是由符号位和数值位 构成的,比如上面例子中,
01101100
就是机器数。
-156D
(真值)= 110011100B
(机器数)
1.2.2. 原码、反码、补码和移码
(1)原码表示法
简单点理解,原码就是符号位加上真值(二进制)的绝对值,同时用逗号将符号位和数值位隔开。比如,+1 就是 0,0000001
,-1 就是 1,0000001
。
原码的特点是简单、直观,但是原码在进行加法运算的时候会出现问题。正数加正数或者负数加负数是正常的,但是正数加负数就会出错。比如我们现在想要计算 -1+3,我们心想:-1 是 10000001
,+3 是 00000011
,加起来得到的是 10000100
,所以结果是 -4,但 -1+3 应该是等于 2,所以这个结果是错的。我们发现,本来应该做的是加法运算,但实际上变成了减法运算(-1-3=-4)。
我们首先想到,可以通过将“正数加负数”转化为“正数减正数”来手动纠正这个错误。上面的例子就变成 00000011
减 00000001
,结果是 00000010
,也就是 2,这个结果是正确的。
但是每次都这样手动转化,计算起来还是太麻烦了。于是我们接着想:有没有一种方法,可以让“正数加负数”中的 负数等价于一个正数,从而确保始终进行的是相加操作呢?
于是这时候就引出了补码的概念。
(2)补码表示法
- 补数和模:理解补码之前,我们先来理解两个概念:补数 和模。
拿时钟举例,想要从 10 点拨到 8 点,有两种做法,一种是逆时针拨 2 个单位,记作 -2;一种是顺时针拨 10 个单位,记作 +10,这两种操作是等效的 (有点 负数等价于一个正数 的意思)。这时候我们就说,-2 是 +10 以 12 为模的补数,记作-2≡+10(mod 12)
,同理,-5 相当于 +7,-4 相当于 +8。
- 那么怎么基于补数和模的概念将“正数加负数”转化为“正数加正数”—— 即怎么令 负数等价于一个正数 呢?假设我们现在有一个寄存器可以存放四位二进制数(此时,模为 16),我们想要让
1011
变成0000
,最容易想到的办法就是1011-1011=0000
,注意这里是正数加负数。想要变成正数加正数,就要找到等价于-1011
的正数,-1011
就是 -11,-11 以 16 为模的补数就是 +5,+5 就是+0101
,这个正是我们要找的那个等价正数,因此这时候,1011-1011
变成了1011+0101
,其结果是10000
,不要忘了寄存器只能存放四位,所以结果其实是0000
,恰好与我们“正数加负数”时得到的结果无异。 接着引入补码的概念:
- 对于正数:正数的补码和原码相同;
- 对于负数:负数的补码等于其原码在保持符号位不变的情况下,其余各位取反,末位加一(取反加一)。注意,补码的补码等于原码,这可以用来根据补码求原码。
- 也可以用下图方法计算补码:
还是上面的例子,1011-1011
,也就是 11-11,我们考虑 +11 和 -11,+11 的原码 = 补码 =01011
,-11 的原码是 11011
,因此补码是10101
,那么01011+10101
就会等于100000
,因为寄存器是五位的,把前面的 1 去掉,那么结果就是00000
,也就是 0,和上面的运算结果一致。
(3)反码表示法
反码很好理解:
- 正数:反码等于原码等于补码;
- 负数:反码等于原码保持符号位不变的情况下其余各位取反。也就是说,补码 = 反码 +1
- 反码的反码等于原码,这可以用来根据反码求原码。
(4)移码表示法
补码存在的问题是,仅从补码本身来看,很难比较两个数的大小,为此引入了移码的概念。移码指的是在真值(二进制)的基础上加上一个偏移量,通常这个偏移量是 2^n。其中,n 是数值位的位数。例如,对于 -10101
,其移码是2^7+(-10101)=10000000+(010101)=0,1101011
。
当然,我们有简单的方法可以计算一个数的移码:不管正数还是负数,其移码都等于补码的符号位取反。
2. 定点数的加减运算
2.1 补码的加减运算
定点数的加减运算实际上就是补码的加减运算。我们来看一个例子:
假设机器字长为 8 位(含 1 位符号位),A=15,B=-24,现在求 A+B 和 A-B。
A 的补码是 0,0001111
,B 的补码是 1,1101000
,那么 0,0001111+1,1101000=1,1110111
,转化为原码,再转化为真值,得到 -9,这是正确的。同理,A-B 就是 A+(-B),-B 的补码是 0,0011000
,那么 0,0001111+0,0011000=0,0100111
,最后转化为真值,得到 +39,这也是正确的。
我们再来看另一个例子:
假设机器字长为 8 位(含 1 位符号位),A=15,B=-24,C=124,现在求 A+C 和 B-C。
我们同样按照上面的流程来进行计算,最后得出:A+C 结果是 -117,B-C 结果是 +108,这两个都是错误的。为什么会出现这样的情况呢?
2.2 溢出
这种情况就叫 溢出 。出现的原因,简单来说就是:运算结果太大了,或者运算结果太小了。就上面的题而言,8 位二进制数所能表示的数字的范围是有限的,当正数加正数的时候,结果可能过大,超出了最大值,此时称为 上溢 ;当负数加负数的时候,结果可能过小,够不到最小值,此时称为 下溢。如下图所示:
我们拿 3 位二进制数来理解这个问题。假设 3 位二进制数可以表示的范围如下:
现在我们进行 2+2 操作,那么就是 010+010
,结果就是 100
,这已经超出了正数可以表示的最大范围,也就是发生了上溢。所以此时得到的是 -4,这是一个错误的结果。
2.3 溢出的判断
前面说过,溢出的原因要么是运算结果太大,要么是运算结果太小,其实从这句话我们可以看出,正数和负数相加是不会发生溢出的,因为其结果必然在可以表示的范围内,唯一可能会发生溢出的情况,要么是正数加正数,要么是负数加负数。那么,如何判断在这两种情况下是否会发生溢出呢?有三个方法:
(1) 一位符号位:比较操作数符号位与结果数符号位
我们还是拿上面的第二个例子解释:
可以看到,A+C 中,两个操作数符号位都是 0,也就是都是正数,但结果数的符号位却是 1,也就是负数,那么很明显它发生了上溢;
同理,B-C 中,两个操作数符号位都是 1,也就是都是负数,但结果数的符号位却是 0,也就是正数,那么很明显它发生了下溢。
(2) 一位符号位:看符号位与最高数值位的进位情况
看第一个式子,进行运算的时候,符号位没有产生进位,但是最高数值位向前产生了进位,这时候判断它发生了上溢;
看第二个式子,进行运算的时候,最高数值位没有产生进位,但是符号位向前产生了进位,这时候判断它发生了下溢。
(3) 两位符号位:
这种方法是将一位符号位改为两位符号位表示:正数符号为 00,负数符号为 11。那么前面的例子就会变为:
因为溢出只发生在两个正数或者两个负数相加的时候,所以两个操作数的符号要么都是 00(正数),要么都是 11(负数)。如果是 00,结果应该也是 00(正数),但是图里结果却是 01,说明发生了溢出,而且是上溢;
如果是 11,结果应该也是 11(负数),但是图里结果却是 10,说明发生了溢出,而且是下溢。也就是说,当我们发现操作数的符号与结果数的符号相异的时候,就可以断定这个操作发生了溢出。
同理,我们回过头看第一个没有溢出的例子:
第一个式子其实一眼就可以断定不会溢出,但我们还是用上面的方法判断一下。操作数符号一个是 00
,一个是 11
,结果数符号是 00+11=11
,所以这是正确的,没有溢出发生;第二个式子,操作数符号都是 00
,结果数符号也是 00
,所以这个式子也没有发生溢出(尽管是两个正数相加,但是并没有超出最大范围)。
参考:
https://www.bilibili.com/vide…