一、加法器原理
这一小节,我们来看新考点补码加减运算器。
也就是从硬件的层面来看,补码的加减运算是如何实现的。
(1)介绍
如下图,这是一个基本的加法器,可以实现 A 加 B 等于 F 这样的运算。
其中 A
和 B
分别是 n 个比特的二进制数, F
是 n 个比特的输出结果。
另外右边还有 Cin
输入信号,一个比特,这是来自于更低位的进位信息。左边的 Cout
是 A 和 B 相加之后,向更高位产生的进位,同样也是 1 比特。
(2)案例
我们结合两个例子来解释一下。
比如这儿的 n 指的是4,也就是加法器实现的是 4 比特二进制数的相加操作。
1.案例一
先看第一个例子, A 的值是1000, B 的值是0111, Cin这个比特等于 0 。
加法器做的事情就是 A 加 B 再加上最低位 Cin 值的信息。
如果列加法的竖式来计算,如下:
因此最后输出的结果 F 就等于1111。
还有一个输出的信号 Cout,最高位 1 + 0 = 1,没有产生进位,所以 Cout这个信号应该是输出 0。
所以第一个例子输出的结果F 就等于 1111, Cout 就等于0。
注意体会 Cin和 Cout 它们的含义。 Cin 是加到 A 和 B 的最低位的值,而 Cout 是 A 和 B 的最高位相加之后向更高位产生的进位信息。
2.案例二
接下来第二个例子, A 和 B 的值分别是1000 和0111; Cin 变为了1,第一个例子是0,现在是1。
A 和 B 相加,在最低位还要加上 Cin。所以加法的竖式应该是这样的:
解释一下竖式子如何计算。对应的位相加, 从最右边开始。0 + 1 + 1 本位和应该等于 0 同时向高位进1;0 + 1 再加 1 本位等于 0 向高位进 1;0 + 1 + 1 本位和等于0向高位进 1;1 + 0 + 1 本位和等于 0 向高位进1。
所以最后得到的 4 个比特的输出结果是0000。
Cout向更高位产生的进位应该是1。这是我们根据 A 和 B 的最高位相加产生的进位信息得到的结果。
所以第二个例子当中, F 就是 0000,Cout 就是1。
注意体会 Cin和 Cout。Cin是要加到 A 和 B 的最低位,而Cout 是 A 和 B 的最高位相加之后,向更高位产生的进位信息。这个进位信息最终会被丢弃,因为相加结果 F 只会保留 n 个比特的信息。
这就是加法器
的基本原理,可以实现两个 n 比特数的相加,同时也可以考虑到来自于更低位的进位信息,以及可以向更高位产生一个进位信息。
像刚才这个例子当中,如果把两个加法器给它串起来,就是,刚才我们的加法器它产生了Cout,如果我们在 Cout 的这一边再连一个加法器,这样的话,右边的加法器向更高位产生的进位信息就可以被左边的加法器所接收。
如果我们想要实现8比特加8比特的加法运算,就可以用两个加法器把它们串起来,这样就可以进行加法的位扩展
,这样整体来看就可以支持更多个比特的加法运算。
这就是基本的加法器。
二、补码加减运算
(1)手算
了解了加法器原理之后,接下来我们要把加法器进行改造,让它能够支持补码的加和减两个运算。
首先我们来回顾一下我们用手算的方式是如何实现补码的加减运算的。
1.方法
<1> n 比特的补码 x+y
,直接按位相加就可以,符号位也同样参与运算,同样要进行相加的操作。
<2> 如果要计算的是 x-y
,我们的处理方法是把减数 y 全部按位取反, 0 变 1、1 变0,然后末位加1,由此就可以得到负 y 的补码。 x 减 y 就可以把它转换成x加上-y的补码。也就是把减法变成加法。
这个思路就是用加法器的电路来实现补码减法运算的基础。
2.案例一
还是用例子来回顾补码的加减运算。
假设我们用 4 比特表示补码 X 的值是-8、Y 的值是7。
那么X 的补码表示应该是 1000, Y 的补码表示应该是0111。
<1> X加Y,就是把对应的比特位依次相加,就可以得到1111。
<2> X减Y,我们做的事情就是把这个减数 Y 全部按位取反。 Y 的值本来是0111,全部按位取反变成了1000,末位再加一个 1,这样我们就得到了-Y的补码。于是我们就可以把 X 减 Y 转变成X加上-Y。
相加之后得到的结果就是0001。
最前边这儿会产生一个进位1,但是这个进位会被我们丢弃,因为我们只用 4 个比特来保存运算结果。
事实上,在这个减法运算当中,此时发生了溢出。因为 4 比特的补码,可以表示的范围应该是-8~ 7
这个范围。
我们尝试着 X减Y,其实要计算的是-8-7
,这个运算结果的真值应该是-15。
但显然, 4 个比特是无法表示-15 这个值的。因此,最高位产生的 1 被我们丢弃了,只保留了末尾的 4 个比特,此时发生了溢出
。
那关于溢出如何判断?我们一会还会继续展开细聊。 这只是为了帮大家回顾一下补码的加减运算用手算的方式是怎么实现的。
3.案例二
下面这个例子。
4 比特的补码 x 等于3, y 等于4, x 的补码应该是 0011 、Y 的补码0100。
<1> x 加y,直接 4 个比特和 4 个比特按位相加就可以。
<2> x 减y,我们做的就是把 y 全部按位取反, 1 变 0、0 变1,末位再加1,由此得到负 y 的补码,把减法转变为加法,最终得到的结果就是 4 个1。
这是补码加减运算的手算方式,最重要的一步处理,就是在运算减法的时候,需要把减数全部按位取反,末位加1,减法变加法。这就是补码减法运算的实现逻辑。
(2)加法器优化
1.原理
接下来我们把刚才的加法器电路进行拓展,用于实线补码的加减运算。
这根蓝色的线上半部分就是刚才我们学习的加法器,下面这个部分就是用于实现补码加减运算的电路逻辑。
来看一下什么原理
。
<1> 我们刚才说补码 x 加 y 这个操作,只需要按位全部相加就可以了。
<2> 如果要实现补码的减法,我们需要把减数全部按位取反、末位加1,减法变加法。
来看一下是怎么实现的。
①首先,无论是加法还是减法, x 的 n 个比特都是不用改变的,所以 x 的 n 个比特直接连到加法器的左边这一端。
②接下来 y 这个数。当进行加法的时候, y 不需要改变,直接输入到加法器的右边就可以。而如果要进行减法,我们需要把 y 全部按位取反、末位加1。
可以看到这儿有一个多路选择器
,如果多路选择器的控制信号为 0 的时候,右边这条线会被打通,这条线的内容就可以从输出端输出。(下图绿色线)
而如果多路选择器的控制信号是1,左边的这条线,这些信号就会被输出。(下图蓝色线)
可以看到这个地方我们有一个控制信号,叫Sub
,当我们要实现加法的时候,控制信号是0;如果要实现的是减法,控制信号就是1。
2.实现加减法
<1> 如果实现的是加法
,控制信号为0,此时右边这条线被打通。而你会发现右边这条线,它是直接把 y 的 n 个比特直接就给连过来了。(下图绿色线)
所以如果要进行加法,就相当于右边这条线被选通,也就是 y 没有做任何处理,直接就输入到加法器的这一端。
Sub 控制信号,你会发现这个地方还有一条线,它连着 Cin。
当我们要实现加法的时候, Cin 信号是0(下图橘色线),所以就相当于 x 和 y 的这 n 个比特分别相加,最低位不加任何东西,这是加法的实现。
<2> 接下来减法
的实现。
如果要实现减法, Sub的控制信号就是1,意味着左边这条蓝色的线会被选通。
可以看到蓝色的线有一个非门,非门的作用就是把这 n 个比特的信息全部按未取反。
之后,左边的这条线路被输出,作为加法器的输入。(下图蓝色线)
这就相当于我们完成了把 y 全部按位取反的操作。
另外,由于此时实现的是减法,所以 Sub 的这根线也会输入1,也就意味着 Cin这个信号就是1,这个 1 是要被加到最低位。这不就相当于我们往y的末位加了一个 1 嘛。
这就是用加法器实现补码的减法运算的原理。
3.案例
接下来我们结合刚才的例子实际走一遍。
x 是1000, y 是0111,都是4比特的补码。真值分别对应- 8 和7。
<1> 首先看加法
如何实现。
当我们要实现加法的时候, Sub 控制信号应该是 0 ,就意味着右边这条线会被选通,0111这些信号会直接通过多路选择器直接输入到加法器的右边,所以加法器右边输入的是0111。
加法器的左边直接就是连着 x 的1000。
Sub 这根线,它会作为 Cin 的输入,这个输入信号是0。这就相当于我们做了1000,加上 0111 ,末位再加了一个0,相加的结果就是1111。
所以最后得到的结果 F 就是1111。如果按照有符号数 4 比特补码,把它解释为十进制数,应该对应的是十进制的-1,这个运算的结果是正确的,- 8 + 7 等于-1。
这是加法的实现。
<2> 接下来再来看减法
的实现。
要实现减法,Sub这个信号应该是1,所以多路选择器的控制信号也是1。
这就意味着左边这条路会被打通,左边这条路连着y,经过了非门之后,全部按位取反变成了1000。
1000 这个信号会通过多路选择器输入到加法器的右边,也就是加法器的右边输入的是1000,
另一个方面, Sub 信号等于1,这就意味着 Cin 输入信号也会输入1。
加法器对这三个部分进行相加的操作,就相当于 1000 加1000,末位还要再加一个1。
这个部分就实现了 y 全部按位取反,末位加 1 这个事情。对应的比特位分别相加,得到的结果应该是 0001还要向更高位进1。只不过更高位的 1 会被我们丢弃,因为最终的结果我们只保留 4 个比特。
所以最后的运算结果应该是0001,最高位的 1 被我们丢弃。如果解释为十进制,就应该对应十进制的1。
显然这个运算的结果是错误的,此时发生了溢出
。
通过这个例子,相信大家能够理解补码的加减运算在电路上是怎么实现的,核心还是一个加法器,但我们如何把减法变为加法,就是要增加下面的电路,同时也要增加一个控制信号Sub,加法的时候信号为0,减法的时候信号为1。
现在建议大家再利用下面这个例子,来感受一下 x 加 y 怎么实现, x 减 y 怎么实现。自己来梳理一遍,你就可以更好的理解电路的原理。
三、无符号数的加减运算
(1)原理
讲到这儿,我们已经学完了补码的加减运算器,重要的是要理解补码的减法如何利用加法器来实现。刚才的电路,可以实现补码的加减运算电路,这个电路同时也可以实现无符号数的加减运算。
因为无符号整数的加减法背后的处理逻辑和补码是一模一样的。
<1> n 比特的无符号数 x 加y,同样按位相加就可以。
<2> 而n比特的无符号数 x 减y,处理方式同样是把减数 y 全部按位取反、末位加1,然后减法变加法。
所以你可以看到无符号数的减法运算,它在背后底层的处理逻辑和补码的减法运算是一模一样的。因此这个电路同样可以被用于无符号数的加法和减法运算。
(2)案例
还是用例子来感受一下。
1.案例
用 4 个比特表示无符号整数, x 取1000(真值为8), y 取0111(真值为7)。
当 x 加 y
的时候, Sub 这个信号等于0,当 x 减 y
的时候, Sub 这个信号等于1。
所以从底层二进制的视角来看,和我们刚才讲的补码运算的例子一模一样。
刚才我们说补码运算的时候, x 的二进制同样是 1000, y同样是0111,同样的加法 Sub 等于0,减法 Sub 等于 1 。
因此通过加法器进行运算了之后,相加和相减的结果从二进制的视角来看都一样。相加的结果是1111;相减的结果是0001,最高位产生的 1 被我们丢弃。这是刚才说的补码运算的例子。
我们再回到无符号数的运算,相加的结果是 1111,相减的结果是0001,最高位的 1 被我们丢弃。
2.有趣的现象
这个地方你会发现一个有趣的现象。
案例一
<1> 如果我们把这些二进制的数看作是无符号整数的话,那么 x 加 y 的无符号整数,1111转换成十进制应该是 15 ,而 x 减 y 得到的0001转换成十进制应该是1。
对于无符号数的减法运算,我们得到的结果是正确的,没有发生溢出。因为无符号整数 x 减 y 相当于 8- 7, 8 减 7 就是1,这个结果是正确的,没有发生溢出。
如下:
<2> 回到我们刚才的补码,虽然二进制的运算结果都一样,但是 x 加 y 的结果,我们把它解释为有符号数的补码,应该是对应十进制的-1。
而x 减 y 的结果,按照有符号数补码对它进行解释,翻译成十进制应该是十进制的1。这个运算结果是发生了溢出的,是一个错误的结果。如下:
因此这个地方想强调的是,补码加减运算以及无符号数加减运算都可以用同一个电路来实现。
但是二者判断溢出的方式有所不同。具体要如何判断溢出,这就涉及到我们的第二个考点标志位
的生成。我们一会再来填这个坑。
案例二
现在我们再把注意力放在第二个例子
当中,不知道大家刚才有没有自己手动模拟一遍。
<1> 无符号数
第二个例子当中, x 是0011, y 是0100,如果要进行 x 加 y,那么 Sub这个信号就是0, x 减 y Sub这个信号就是1。
加法运算的结果就是0111,解释为十进制,对应 7,3 + 4 = 7,这是正确的;
减法运算的结果,得到的二进制是1111,按照无符号整数
解释,二进制数对应的是十进制的15,显然这是错误的,3- 4 等于15,这是错误的。
3- 4 是一个负数,但是显然无符号整数没办法表示负数,所以最后得到的结果 15 肯定是错误的。
这是我们按照无符号整数来处理得到的结论。
<2>有符号数
刚才我们补码
的加减运算的第二个例子,其实二进制层面也是一模一样的。
你会看到刚才给出的第二个例子 x 同样是0011, y 同样的是0100。只不过我们会按照有符号数
的补码来解释这两个数。
两个数相加得到的结果是 7,3 + 4 = 7,这是正确的。两个数相减得到的结果1111 ,把它转换成十进制,应该是对应-1。
所以你又会发现,在补码的运算当中, x 减 y 得到的结果是正确的,没有发生溢出。如下:
这两个例子希望大家认真体会。
计算机底层的硬件在处理加法或者减法的时候,他不会管你给他的数到底是有符号数还是无符号数,底层的硬件都是按同一套电路来处理的。
但是最后在我们判断是否发生了溢出的时候,有符号数的判断逻辑和无符号数的判断逻辑是存在显著的区别的。
有符号数的加减法和无符号数的加减法分别又是怎么判断溢出的呢?这个问题我们留到下一个考点标志位的生成来解释。