CSAPP 02:计算机是如何完成整数计算的?
Intro
上一篇里我们已经知道了数据怎么表示,现在,我们该来使用这些数据做一点有用处的事情了。
二元布尔代数
上一篇中我们已经重复强调了,计算机只能看到电信号,它不知道什么是数字。只能识别一串的高低电平信号。
在
二元布尔代数有True与False两种符号(我们将它编码为1
和0
),与AND、OR、NOT三种基本运算。
- 用
&
来表示AND,它有两个输入一个输出,只有在输入全为1
时才输出1
,其他情况下全部输出0
。 - 用
|
来表示OR,它一样有两个输入与一个输出,并只有在输入全为0
时才输出0
,其他情况下全部输出1
。 - 用
~
来表示NOT,它只有一个输入与一个输出,输入1
时输出0
,输入0
时输出1
。
这时候我们发现,AND与OR是可以转化的,只要用NOT反转输入与输出即可。
如果我们只对AND的输出取反,我们就能得到一个NAND,只用它,我们能重新构建出AND、OR和NOT。
在现实里,使用晶体管可以简单地制造出一个有NAND逻辑的电路元件。
对OR的输出取反得到的NOR也有这种能重新构建基本运算的性质。
于是,我们能将电路转化为布尔代数运算了。
接下来我们要构建一种运算XOR,它有两个输出一个输入,只在两个输出不相同时输出1
,用^
来表示。
注意这里的运算符号没有优先级,全部从左往右算即可。
^(In1,In2) -> (ans) :
Input1 & (~Input2) | (~Input1) & Input2
于是我们构建了XOR,遇到XOR就将其展开为顶上的式子即可。
ADD & SUB
接下来我们来开始构建加法。
得益于二进制数的简单,我们可以轻易列出一位二进制加法的所有可能性。
加数 | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 10 |
我们会发现,可能的输出里有一个两位数,这意味着一位的输出是不够的。
也就是说,如果有一个表示一位加法的运算符,它就至少得有两个输入与两个输出,否则无法表示所有可能的结果。
假设我们用和来表示一位数,用进位来表示第二位数,我们就能构造加法。
于是,我们认为这个加法运算有两个输出:和SUM
、进位CAR
。
# In1即Input1表示第一个输入,所有的In符号同理。
SUM = In1 ^ In2
CAR = In1 & In2
这个元件就是一个半加器。
为什么说它是“半加器”?因为它只能处理一位的二进制加法。
我们当然不只是想处理一位的二进制加法,为此我们需要再对它做一点改造。
我们要给他加入第三个输入:进位CARIN
。
这是一个特殊的一位输入,但我们可以简单的将它视作是一个一位的二进制加数。
于是我们重新构建一位加法:
1bit_plus(In1,In2,CARIN) -> (SUM,CAR) :
SUM = (In1 ^ In2) ^ CARIN
CAR = (In1 & In2)|(In2 & CARIN)|(In1 & CARIN)
这个就是一个全加器,它与半加器最大的区别便是它能通过堆叠扩展成任意位数的二进制加法。
这是为什么?
想象一下我们在算加法时使用的竖式,它展示了一种将一个多位加法拆解成多次的一位加法的方法。现在我们把它扩展到二进制数,我们会发现情况变得十分简单了,我们可以将一个位数的二进制数加法拆解成多次的一位加法,而我们已经构造出一个一位加法了。
11100101
+ 01010110
--------
并且我们已经知道了,一个多位的二进制数事实上是一串一位二进制数序列。使用这种堆叠加法是一件十分显然的事情。
于是,我们就构建出了任意位数的加法。
减法可以被视为加一个数的相反数,为此我们来构建一个取反运算。
前面我们已经知道了怎么表示一个负数,对一个数取反的方法其实就是将它的每一位进行NOT运算之后再加一。
以0100
为例,它的相反数是1100
。
-(In) -> (ans):
ans = ~In + 1
于是,我们顺利构建出了加法与减法,并且这些操作都可以实现成一个具体的电路。
这些内容事实上可以通过一个游戏Turing Complete清晰地学到。
溢出
在我们知道加减法究竟意味着什么之后,我们就能知道溢出是发生了什么了。
无符号加
以四位加法来举例:
当我们试图计算1111 + 0001
时,输出结果事实上是SUM=0;CAR=1
。但假设我们要将这个结果保存在一个四位的储存器中时,CAR
就被舍弃了。
这时就发生了溢出。
将这个情况往高位扩展也是同样的,无符号加法会在最高位产生进位时将它舍弃掉。
这种舍弃是必不可少的,因为减法一定程度上依赖于这种舍弃的机制运行。
无符号减
通过刚才的过程我们已经知道了减法是通过加一个相反数实现的。事实上可以说无符号减就是依赖于溢出这个机制来顺利执行的。
以刚刚的4-4
为例
0100
+ 1100
----
1 0000
可以看到当最高位的1(CAR
)被舍弃掉之后结果(SUM
)是正确的。
但减法是会产生负数的,当结果是负数的时候会产生借位
以2-4
为例
0010
+ 1100
----
1110
我们发现结果是14,但如果我们将它解释为一个有符号整数,它的值就是对的(-2)。
这种情况我们就称它发生了借位。
有符号加减
有符号数的加减法并不依赖于溢出机制,于是执行有符号数的加减法不可避免的会有上溢与下溢的问题。
如以四位长度计算0111 + 0001
结果超出了最高的表达范围,变成了1000
。这种情况就称为正溢出。
相似的,若计算1000 + 1000
,其结果产生了进位(1 0000
),这时SUM=0000;CAR=1
。我们称这种情况是负溢出。
Outro
于是我们就完成了加法与减法的定义,我们不难发现,即使一个机器没有人的智慧,仍然可以正确的完成计算。
到此,我们已经构想出了一台可以进行加减法计算的机器,下一篇中我们将试着把它实现出来。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通