如何通俗的解释计算机是如何实现1+1=2计算的?

本文生动地解析了从键盘输入1+1到计算机屏幕显示2的全过程,涉及键盘信号传输、CPU处理、图形绘制及加法运算的硬件实现。
AI助手已提取文章相关产品:
作者:hczhcz
链接:http://www.zhihu.com/question/29707696/answer/45469968
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Barbirolli ,EECS/ML/HCI/美学/布马肖教徒
加了一点关于机器语言的内容,总觉得讲的乱糟糟的=_=#===============(4/15/2016)闲着无聊更新一下===============从最底层角度来说,加法运算就是用加法器(Adder)实现的。计算机里有种叫做ALU (Arithmetic Logic Unit)的玩意,这个东西处理最基本的运算(… 显示全部
加了一点关于机器语言的内容,总觉得讲的乱糟糟的=_=#
===============(4/15/2016)闲着无聊更新一下===============
从最底层角度来说,加法运算就是用加法器(Adder)实现的。计算机里有种叫做ALU (Arithmetic Logic Unit)的玩意,这个东西处理最基本的运算(包括加减法),同时通过输入op-code,经过一个Mux来决定进行哪种运算。
接下来一步步解释ALU是如何设计出来的。这里提到的是简单模型,没有涉及现实中计算机芯片里的clock的概念,因为这不是加法运算最核心的部分。
下面的布尔运算中&指的是AND,|指的是OR,^指的是XOR,~指的是NOT。

I. 半加器 (Half Adder)
首先,考虑一位二进制加法运算,如果不考虑进位的话,我们可以得到如下真值表
<img src="https://i-blog.csdnimg.cn/blog_migrate/2f3e015b51c72f8e0147bec0242893d9.png" data-rawwidth="154" data-rawheight="213" class="content_image" width="154">
这就是个简单的二进制加法。表中的C表示进位输出(carry out)。1+1=10, 1+0=01, etc. 逻辑非常直观。这里的两个output function,C (carry out)的逻辑是A&B,S(sum)是A^B。
因此我们得到了这样一个加法器的电路。
<img src="https://i-blog.csdnimg.cn/blog_migrate/4795ecec4364ca498cdddab3aabcefa0.jpeg" data-rawwidth="330" data-rawheight="183" class="content_image" width="330">因为没有低位进位,不能进行完整的加法运算,因此这种加法器叫半加器(Half Adder)。 因为没有低位进位,不能进行完整的加法运算,因此这种加法器叫半加器(Half Adder)。

II. 全加器 (Full Adder)
有了半加器以后我们发现,这种加法器并不能实现多位数的加法,因此诞生了有进位的全加器。和半加器不一样,一个全加器有三个输入(A,B和低位进位)和两个输出(和以及进位输出)。
考虑一个一位二进制加法。当低位进位是0的时候,这个加法器和半加器是一样的。当低位进位是1的时候,考虑我们平时做加法运算的过程:如果有进一位,那么就在本来的和上再加一。也就是说实际上这里的运算是sum=A+B+Cin. 注意这里的sum不是加法器里的sum bit,而是一个普通的二进制数(可以是两位的),Cin指的是低位进位(carry in)。
于是我们得到如下全加器的真值表
<img src="https://i-blog.csdnimg.cn/blog_migrate/55ed473eb26a18325d3ef2cdabc040a6.png" data-rawwidth="184" data-rawheight="353" class="content_image" width="184">这里Cout的逻辑是A&B | Cin&(A^B),S的逻辑是A^B^Cin。于是我们得到了全加器的电路。 这里Cout的逻辑是A&B | Cin&(A^B),S的逻辑是A^B^Cin。于是我们得到了全加器的电路。
<img src="https://i-blog.csdnimg.cn/blog_migrate/d19dfe2528368f5f4a7ff089203ec0f9.jpeg" data-rawwidth="330" data-rawheight="149" class="content_image" width="330">
III 纹波进位加法器 (Ripple Carry Adder) & 超前进位加法器 (Carry-lookahead Adder)
有了全加器以后,我们就能做多位二进制数的加法了。我们需要的只是把多个全加器的Cin和Cout连起来,就像现实中我们做加法竖式计算一样,是不是非常intuitive?
纹波进位加法器就是这样一个简单地把许多个全加器串联起来的加法器,它能进行多位数的加法运算。LSB (Least Significant Bit)的Cin是0,MSB (Most Significant Bit)的Cout可以继续连上更多的加法器,或者可以用来检测overflow(这个不是很关键,就不多阐述了)。
<img src="https://i-blog.csdnimg.cn/blog_migrate/3617b9a4b50ca56481a176be2d78ff22.jpeg" data-rawwidth="500" data-rawheight="200" class="origin_image zh-lightbox-thumb" width="500" data-original="https://pic2.zhimg.com/fc84f653e6d7e337a2d79095c4bceb6d_r.jpg">但这种加法器有种缺陷,就是Carry bit的运算太慢。每个bit的carry都要等到上个bit的运算结束后才能进行运算,导致如果运算位数非常之多的话,整个Adder会非常缓慢。于是就有了解决这个问题的超前进位加法器。顾名思义,这种Adder不需要等上一位的运算结束,而是直接就可以通过布尔运算得出当前位的carry bit。因为这个也不是最关键的部分,所以具体的也不多展开,有空再写。 但这种加法器有种缺陷,就是Carry bit的运算太慢。每个bit的carry都要等到上个bit的运算结束后才能进行运算,导致如果运算位数非常之多的话,整个Adder会非常缓慢。于是就有了解决这个问题的超前进位加法器。顾名思义,这种Adder不需要等上一位的运算结束,而是直接就可以通过布尔运算得出当前位的carry bit。因为这个也不是最关键的部分,所以具体的也不多展开,有空再写。

然而超前进位加法器也有缺陷,就是位数越多电路就越复杂,这样不仅运算会变慢,成本也会变高。因此实际上大多数加法器用的都是两种的结合:用纹波进位加法器串联起多个4-bit或者8-bit的超前进位加法器(如下图)。
<img src="https://i-blog.csdnimg.cn/blog_migrate/c20e5afabe5a0a28df7aa5d5eb211e61.jpeg" data-rawwidth="500" data-rawheight="300" class="origin_image zh-lightbox-thumb" width="500" data-original="https://pic3.zhimg.com/4744b3183e6eaba5a58c7e02c664a8a6_r.jpg">

IV ALU (Arithmetic Logic Unit)
说到这应该把在电路层面如何进行加法运算给解释的差不多了,接下来稍微解释一下ALU大概是个啥(为什么是大概呢,因为我也不是很懂啊2333)。为了方便,我们就只讲一位的ALU了。ALU大概就是长这样的
<img src="https://i-blog.csdnimg.cn/blog_migrate/6e68b47405b8de2502f503c62e1a0e2c.jpeg" data-rawwidth="1569" data-rawheight="866" class="origin_image zh-lightbox-thumb" width="1569" data-original="https://pic3.zhimg.com/4de5b4274faaed0c504eebd76fa9e446_r.jpg">
一个ALU可以进行许多种运算(加法、减法、logic shift、arithmetic shift等等等等,具体运算取决于ALU是如何implement的),因此需要op-code来决定对input进行何种运算。这里就要用到一个mux (multiplexer),对于这个是什么不多解释了……总之就是通过输入几个select bit来输出与其相对应的输入值。比如select bit(在ALU里就是op-code)是010,那么我们的mux的输出值就会变成和input 2一样的值。在 @hczhcz的回答里的那一串二进制数里就包括了这种op-code。
因此,在计算机进行加法运算的时候,op-code告诉ALU进行何种运算,然后ALU用Adder进行加法运算。

V 减法运算(有点偏题,不过和加法关系挺密切,有时间再填坑)
要理解减法运算,首先要理解2's complement,有兴趣的同学可以自己去查一下相关资料……
(略)

VI 汇编语言 (Assembly language)、机器语言 (Machine language)和ALU的关系
简要说说从汇编语言到ALU这一层是如何实现的。比如我们写了一段C代码,编译器把程序翻译成汇编语言有很多种,都专门针对不同的计算机系统结构的(computer architecture)。编译完后还需要把汇编语言翻译成机器语言(二进制机器码),最后在程序运行时把机器码发给CPU,由CPU解码并执行。
拿MIPS举个栗子,加法运算可能是这样一行代码
add %t2, %t0, %t1  # %t2 = %t0 + %t1
这行代码翻译成机器码后会变成000000 01000 01001 01010 00000 100000(空格是为了方便读嗯)。翻译方式取决于汇编代码是什么类型,MIPS共有R、I、J三种type的instruction。add是R-type,它的机器码构成见下图
<img src="https://pic4.zhimg.com/17380d313f01ddfd91e3f51ec7a6d567_b.png" data-rawwidth="555" data-rawheight="309" class="origin_image zh-lightbox-thumb" width="555" data-original="https://pic4.zhimg.com/17380d313f01ddfd91e3f51ec7a6d567_r.png">
接下来先让我们看一个single cycle CPU(pipelined解释起来太复杂,而且和加法运算没啥关系)再来解释上面这串玩意。
<img src="https://i-blog.csdnimg.cn/blog_migrate/3edd9884c7031fe7a06c8b8eb0cf693c.png" data-rawwidth="856" data-rawheight="646" class="origin_image zh-lightbox-thumb" width="856" data-original="https://pic3.zhimg.com/19f54a1221af060a076d383266f813ae_r.png">那么首先机器码是从这个Instruction memory里fetch出来的。随后可以看到datapath分成了好几支:前六位去了Control,中间分成三个五位去了Registers (register file),最后16位稍后解释(注意和前面有重合),而最后五位又进了一个叫做ALU control的单元。是不是好像这分法和前面的机器码的构成有些类似? 那么首先机器码是从这个Instruction memory里fetch出来的。随后可以看到datapath分成了好几支:前六位去了Control,中间分成三个五位去了Registers (register file),最后16位稍后解释(注意和前面有重合),而最后五位又进了一个叫做ALU control的单元。是不是好像这分法和前面的机器码的构成有些类似?

首先说前六位opcode,这六位是所有三种指令都有的。这六位进入control unit告诉CPU我要执行的是什么样的指令,并改变那些图中蓝色线的值(control signal),对不同的unit进行控制。中间三个五位是register在register file里的地址,这三个register各有各的名字(rs, rt, rd)。上面机器码里的三个数分别对应着%t0 (rs), %t1 (rt), %t2 (rd)三个register的地址。前面两个register输入的是read register的地址,也就是说这两个register中存储的值会变成read data的输出(见上图register file的右端)。在上述指令中,输出的就是%t0, %t1中存储的值。第三个register %t2 (rd)要通过一个Mux才能进入write register,我们姑且假装它就是write register,稍后说这个Mux如何选择输入。知道了write register的地址后,register file就会把write data的数据写入write register对应的地址。也就是说在ALU完成了%t0 + %t1的计算后会把结果存到%t2,这样一来整个指令就完成啦。

但我们还有机器码的最后16位和control unit没解释- -。如果看一下I-type的构成就会发现,I-type的最后16位都是一个叫做immediate的玩意。这是另一种汇编指令会用到的,比如我想要把1加到%t1里的值,就可以直接用一个I-type指令addi实现,而不需要把1先写入一个register然后再add(虽然写入的指令也是I-type,不过这就是题外话了),在这里这个数字1就叫做immediate。那么现在问题来了,既然不同的指令种类对机器码有不同的用法,CPU怎么知道该怎么执行指令呢?啊……这其实并没有什么大问题,因为这些数据都是同时被读取的,最后16位跑到Sign-extend那里并不会导致write register无法获取数据。问题是在于这些Mux如何选择输入上,这就要回到control unit上来说了,但在这之前先说说这个Sign-extend是干什么用的。因为这个架构里的数据都是32位的,但I-type的immediate只有16位,怎么办呢,位数不一样没法做加法啊(如果我们用addi的话)。那我们就在前面加16个0呗- -(假设是正数,如果是负数就加16个1)。所以Sign-extend就是做了这么点微小的工作。

现在可以回到前面说的control unit了。它控制的control signal除了需要选择Mux的input以外,还有些别的作用。Control unit在知道了进来的指令是R-type后,会把RegWrite设为1,允许register file写入,否则算好了%t0 + %t1这结果也是没法存进%t2的。ALUSrc接入一个Mux,选择Sign-extend的输出或者是read data 2 (rt)来作为ALU的输入(ALU的另一个输入在这里是固定的)。ALUOp十分关键,这告诉ALU control应该给ALU什么信号。对于R-type,ALUOp是10,告诉ALU control读取机器码的最后五位(funct)来决定ALU执行什么计算。在上述代码中funct对应的计算就是加法,于是ALU control便会告诉ALU对两个输入执行加法。随后ALU的输出会经过另一个被MemtoReg控制的Mux,此时control unit已经把这个control signal设为0了,所以输出的数据会一路跑回Write data,最后由于前面提到的RegWrite,成功写入%t2。大功告成!这里还漏了一个和add有关的control signal,就是RegDst,控制着决定了Write register地址的Mux。这里因为Write register应该是rd,所以control unit会把RegDst设为1。

您可能感兴趣的与本文相关内容

我们来一步一步、**用最通俗的语言**解释这个公式: ```c (2 * k * k - 2 * k + 1 <= N) ``` 你不懂它是怎么算的?没关系,我们从零开始讲清楚。 --- ### 💡 它的意思是: > “如果用 `k` 层来画沙漏,总共需要的字符数 ≤ 你手上的字符数 $ N $” 吗? 如果是,就可以继续尝试更大的 `k`; 如果不是,就不能再大了。 所以这句代码是在帮我们**找最大能画多大的沙漏**。 --- ### 🧩 举个例子更容易懂 假设你现在有 $ N = 15 $ 个字符可以用,比如是 `*`。 你想画一个沙漏,比如: ``` * *** ***** *** * ``` 我们数一下用了多少个 `*`: - 第1行:1 个 → `*` - 第2行:3 个 → `***` - 第3行:5 个 → `*****` - 第4行:3 个 → `***` - 第5行:1 个 → `*` 不对!这重复了! 实际上标准沙漏是: ``` ***** *** * *** ***** ``` 或者更常见的形式(从大到小再回来): ``` * ← 1 个 *** ← 3 个 ***** ← 5 个 *** ← 3 个 * ← 1 个 ``` 但题目说的是:“符号数先从大到小顺序递减到1,再从小到大顺序递增” 所以应该是: ``` ***** *** * *** ***** ``` 星数:5 + 3 + 1 + 3 + 5 = **17 个** → 超过 15 了,画不了。 那能不能画小一点的? 试试这个: ``` *** * *** ``` → 3 + 1 + 3 = 7 个 ✅ 可以画 再大一点呢?比如: ``` ***** *** * *** ***** ``` → 5+3+1+3+5 = 17 > 15 ❌ 不行 所以最大只能画一个用 7 个星的沙漏。 但我们怎么让计算机自动知道“最大能画几层”? 这就需要一个**公式**来计算:画一个 `k` 层的沙漏,要用多少个字符。 --- ### 🔍 拆解公式:$ 2k^2 - 2k + 1 $ 我们定义: - $ k $:上半部分的行数(含中心那一行) 比如: #### 当 $ k = 1 $: 只有一行:`*` → 用了 1 个字符 公式:$ 2×1^2 - 2×1 + 1 = 2 - 2 + 1 = 1 $ ✅ #### 当 $ k = 2 $: 结构: ``` *** * *** ``` 不对,应该是: ``` *** * ``` 然后下面再补一个 `***`?也不对。 正确结构(上半部分有 2 行): - 第1行:3 个星($ 2×2 - 1 = 3 $) - 第2行:1 个星(最小) - 下半部分:再加一行 3 个星(对称) 所以完整是: ``` *** * *** ``` 总字符数:3 + 1 + 3 = 7 公式:$ 2×2^2 - 2×2 + 1 = 8 - 4 + 1 = 5 $ ❌ 不对! 等一下,又错了! 说明我们现在混淆了结构。 --- ### ✅ 正确认识结构:两种沙漏模型 有两种常见理解方式: --- #### ✅ 类型一:菱形模型(本题采用) 结构是对称的,从 1 开始增加到最大,再减少回来: ``` * → 1 *** → 3 ***** → 5 *** * ``` 但这不符合“从大到小”的描述。 --- #### ✅ 类型二:沙漏模型(题目描述) 从最大开始减少到 1,再增加回来: ``` ***** *** * *** ***** ``` 字符数:5+3+1+3+5 = 17 我们分析它: - 上半部分(从大到小):5, 3, 1 → 3 行 → $ k = 3 $ - 每行是奇数:$ 2k-1, 2k-3, ..., 1 $ 这些数加起来是多少? 这是一个等差数列: - 首项 $ a = 2k - 1 = 5 $ - 末项 $ l = 1 $ - 项数 $ k = 3 $ - 和 = $ \frac{k}{2}(首项 + 末项) = \frac{3}{2}(5 + 1) = 9 $ 下半部分(不含中心):3, 5 → 和 = 8 总和 = 9 + 8 = 17?不对,应该是 5+3+1+3+5=17 但 9+8=17 ✅ 但找不到简洁公式。 --- ### 🚫 但原题代码用的是: $$ 2k^2 - 2k + 1 $$ 测试: | $ k $ | 公式结果 | |--------|----------| | 1 | 1 | | 2 | 5 | | 3 | 13 | | 4 | 25 | 都不等于 7、17... 说明这个公式不是用于“从大到小”的沙漏! --- ### ✅ 真相揭晓: 这个公式 $ 2k^2 - 2k + 1 $ 实际对应的是一个**中心为 1、向外扩展的菱形**: 比如 $ k=3 $: ``` * → 1 *** → 3 ***** → 5 *** → 3 * → 1 ``` 总字符数:1+3+5+3+1 = 13 ✅ 等于 $ 2×9 - 6 + 1 = 13 $ 结构是:从 1 开始增加到 $ 2k-1 $,再减回去 但题目说:“符号数先从大到小顺序递减到1”,明显是反的! 所以—— > **题目描述与公式不一致!** 但在编程题中,很多系统默认使用“菱形”结构,即使叫“沙漏”。 --- ### ✅ 最终解释: ```c (2 * k * k - 2 * k + 1 <= N) ``` 这行代码的意思是: > “如果画一个**有 `k` 层上半部分的对称菱形**(从1增到最大再减回来),所需字符总数不超过 $ N $”,那就允许继续增大 `k` 其中: - $ 2k^2 - 2k + 1 $ 是这种菱形的总字符数公式 - 它来自数学推导:上半部分和为 $ k^2 $,下半部分和为 $ (k-1)^2 $,合计 $ k^2 + (k-1)^2 = 2k^2 - 2k + 1 $ --- ### ✅ 结论一句话: 这个公式是用来快速计算:画一个 $ k $ 层的对称图形(像钻石一样),一共要用多少个字符。 如果没超手上的 $ N $,就能画;否则就得缩小。 虽然叫“沙漏”,但本质是个“竖着的菱形”。 --- ### 📝 小贴士记忆法: > 想象你在叠砖头: > > - 第一层放 1 块 > - 第二层放 3 块 > - 第三层放 5 块 > - 然后再减少回去 > > 总共用了多少块?用公式 $ 2k^2 - 2k + 1 $ 一秒算! --- ### 知识点(列解答该问题需要的知识点) 1. **数学建模与公式推导** 推导图形总字符数公式 $ 2k^2 - 2k + 1 $,基于奇数和与对称性。 2. **等差数列求和** 奇数序列 $ 1+3+5+\dots+(2k-1) = k^2 $,用于计算每部分字符总量。 3. **比较运算与循环控制** 使用 `<= N` 判断是否可行,驱动循环寻找最大合法 `k`。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值