added draft

This commit is contained in:
ClovertaTheTrilobita 2026-09-10 16:25:52 +08:00
parent ec0acdec59
commit 1dc17dfc8c

View file

@ -0,0 +1,210 @@
---
title: "如何教会计算机算术??加减篇"
pubDate: 2026-08-04
description: '欢迎来到琪露诺的奇妙数学教室,让我们从……加减法开始吧!'
author: "三叶"
image:
url: "https://files.seeusercontent.com/2026/08/04/fkE6/pasted-image-1785836458506.webp"
alt: "bintree"
tags: ["学习笔记", "计算机组成原理"]
---
1+1等于几?
这个问题困扰了人们很多年。
有人说,一加一等于1,因为两个麦芽糖黏在一起还是一个麦芽糖。
有人说,一加一等于2,因为双击`计算器.exe`,输入1+1,输出结果是2。
有人说……
<br>
……我编不下去了。
那么各位小朋友大朋友早上中午晚上好。今天我们要来教会一台计算机如何进行加减法。
就是这么简单。
咦?其实不简单吗?
## 零、让我们从零开始
假设你的老妈在厨房大吼:“崽啊,快来吃饭!菜都要凉辣!”但是你此时没有办法迅速赶到餐桌,因为你正在忙着偷偷用老妈的支付宝购买*那个游戏6*!你的手边只有餐厅吊灯的神秘开关,你要怎么像老妈传达信息呢?
很简单!控制灯的亮起即可!我们将点亮的灯泡视为`True`(或者更简单地:`1`),熄灭的灯泡视为`False`(或者`0`),这样,你就可以高效地传递信息!
随后你毅然决然地关闭了餐厅的灯,坏结局,你的老妈并不知道高深莫测的计算机组成原理,从她的视角来看……
<i>小崽子又欠揍了(抄起衣服架子)。</i>
怎么办,世界末日就在眼前!但是别慌,因为老妈还有10秒才会破门而入,在这么充裕的时间内足以我们开启下一章节了。
## 一、数字在计算机中的表达
### 1. 二进制
现在,想象你手中拿的是一根电线,没错,就是连着餐厅吊灯的那条电线,回想一下,你是怎么通过电线进行信号传输的呢?
那就是:通电代表`1`,未通电代表`0`。
但是这只能表达两种状态啊,这个世界不是非黑即白的,如果我们需要表达四种状态怎么办?
简单,再加上一根电线。
现在我们可以通过控制两根电线来表达`00`,`01`,`10`,`11`这四种状态,那给它们起个编号吧!
`00`代表`0`;`01`代表`1`;`10`代表`01+01`,进一位,是`2`;`11`就是`10+01`是`3`;
没错,这就是**二进制**表示数据。
在计算机中,我们普遍使用二进制来计算基础运算,这其实是个很显而易见的道理,如果使用十进制,我们需要针对十种不同的状态设计十个不同的电路,而二进制就很简单了,用一根简单的电线,通电代表1,没通电代表0。
#### 二进制的转换
那么假如给你一串随机的**二进制数**:`010110`,我们怎么知道它对应的是多少呢?
我们先来看看十进制数。
随便取一个十进制数:`21367`,它可以很直观地被拆成
$$
2\times10^4+1\times10^3+3\times10^2+6\times10^1+7\times10^0=21367
$$
那么很直观地我们能想到,对于<b>r进制</b>的数`21367`,我们可以表达成
$$
2\times r^4+1\times r^3+3\times r^2+6\times r^1+7\times r^0=21367
$$
更通用地,对于<b>r进制的数</b>$K_nK_{n-1}K_{n-2}...K_1K_0$,可以表达为
$$
K_nr^n+K_{n-1}r^{n-1}+K_{n-2}r^{n-2}+...+K_1r^1+K_0r^0
$$
感觉晕头转向?我们直接拿这个**二进制数**`010110`为例:
$$
010110 = 0\times2^5+1\times2^4+0\times2^3+1\times2^2+1\times2^1+0\times2^0
$$
于是它所对应的十进制数就是:$2^4+2^2+2^1=16+4+2=22$
怎么样,是不是很简单?
#### 十六进制数
你一定听说过这么个名词:**32位操作系统**和**64位操作系统**。
没错,**32位操作系统**代表着,对于`int`整形的变量,一共有32个**比特**用来存储这么一个数。
也就是说,二进制数的长度最长可以有32位。
好多!那么程序员在调试的时候总不能用手指头指着屏幕上的0和1一个一个数过去吧?
于是我们在此引入**十六进制数**。
我们规定,$A$代表$10$,$B$代表$11$,$C$代表$12$,以此类推,$F$代表$15$。
那么完整的16进制个位数便是:$123456789ABCDEF$
你惊人的注意力一定注意到了,这刚好对应**4位2进制数**!
于是,我们可以将一个很长的**二进制数**`01101011`聚合为两个**十六进制数**`6B`
### 2. 二进制数的编码表示
#### 机器数
现在想必你已经能够熟练的使用二进制数进行加减法了,那我们来想想这么一个情况。
假设一个计算机的寄存器中,只有8个位置,即只能存8个0或1,我们该如何存正数和负数呢?
很简单,拿出第一个位置,规定:
<b>第一个位置存0,代表这是一个正数;存1,代表这是一个负数。</b>
那么我们能表达的最小数字便是:`11111111`即$-127$
最大数是:`01111111`即`127`
但是接下来的问题头疼了,我们怎么对他们进行运算?
#### 原码vs补码
我们知道,为了尽可能简化CPU内部部件以实现资源的最大化利用,我们的CPU基础部件只能进行加法运算。
那么对于两个正数,我们很好计算,比如$1+1$:
$$
1+1=0000\ 0001+0000\ 0001 = 0000\ 0010
$$
我们尝试把它变大一点:
$$
127+3=0111\ 1111+0000\ 0011=1 \ 0000\ 0010
$$
多出来了一位!然而很可惜,我们计算机只能存<b>8个bit</b>,所以多出来的`1`我们只能忍痛丢掉,计算结果就是
$$
0000\ 0010 = 2
$$
这就是计算过程中发生了<b>溢出</b>。导致计算结果和实际结果不一致。
但是你又用惊人的注意力发现了:计算结果是2……这刚好就是……?!
$$
130\bmod 128=2
$$
没错,我们这样子计算,刚好就是在将计算结果对$128$进行<b>[模运算](https://zh.wikipedia.org/zh-cn/%E6%A8%A1%E7%AE%97%E6%95%B8)</b>!
那我们是不是可以巧妙地利用这一特性,通过加法来实现减法呢?!
比如说我们有两个数:$50$和$30$,我们需要计算$50-30$
既然在这个计算机中,我们计算的结果都是
$$
(A+B) \bmod 128
$$
那$50-30$就可以看作
$$
\begin{aligned}
[50+(-30)]\bmod 128
&=[50+(-30\bmod 128)]\bmod 128\\
&=[50+98]\bmod 128\\
&=148\bmod 128\\
&=20
\end{aligned}
$$
哇!天才!<del>台下观众欣喜若狂!</del>
对于二进制数来说,我们怎么表达它呢?
我们在此引入**补码**的概念。
$-30$的二进制原码是:`1001 1110`
现在我们除了符号位以外,全部给他取反得到反码:`1110 0001`
再给它末位加上一,就可以得到**补码**:`1110 0010`。
对于正数,我们则规定**补码就是其本身**。
那么对于$50-30$,我们可以在运算时使用$[50]_{补}+[-30]_{补}$,对于这个计算就变成了
$$
0011 \ 0010 + 1110\ 0010 = 1\ 0001\ 0100
$$
丢弃溢出的最高位,我们得到`0001 0100`也就是$20$。
#### 补码的其他好处
如果使用原码,我们会发现一个很尴尬的情况:0有两种表示方法。
即`1000 0000`表示`-0`,`0000 0000`表示`+0`。
但在补码中,我们可以规定`1000 0000`表示$-128$,这样就比原码能多存一个数字!表示范围也扩展为$-128$~$127$。
#### 有符号数vs无符号数
以上的带符号的数我们称为**有符号数**。它们在计算机中以**补码**形式存储。
但是对于一些其他需求,我们不需要带符号的数,那么我们在计算机中就直接以**原码**的形式存储,并且**不带符号位**。
例如在一个8位操作系统中,你使用
```C
unsigned int i = 129;
```
那么它存的就直接是`1000 00001`