前一篇先看了 CPU 里哪些部件负责计算、保存和控制。这一篇把二进制的读法和计算单独整理出来,后面搭运算电路时,就能先判断结果应该是什么。
下面先按无符号整数,也就是 0 和正整数来计算。负数和补码,留到需要处理它们时再展开。
1. 二进制怎样表示一个数?
十进制用 0 到 9,某一位满十就向左进一。二进制只用 0 和 1,某一位满二就向左进一,所以数到 1 以后,下一个数就是 10。
| 十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| 二进制 | 0 | 1 | 10 | 11 | 100 | 101 | 110 | 111 | 1000 |
一位就是一个只能放 0 或 1 的位置。 八位组成一个字节;一个数用多少位来放,就叫它的位宽。101 和 0101 都表示 5,前面的 0 可以用来补齐约定的位宽。
十进制从右往左是个位、十位、百位,二进制从右往左则是 1、2、4、8、16、32……。每往左一位,代表的大小翻一倍。这个位置代表的大小,叫位权。
位权: 8 4 2 1
二进制 0101: 0 1 0 1
0101 = 0×8 + 1×4 + 0×2 + 1×1 = 5
每一位都能选 0 或 1,所以四位一共有 2×2×2×2 = 16 种组合,表示 0 到 15。一般来说,n 位有 2^n 种组合,无符号数的范围是 0~2^n−1。
2^n 表示 n 个 2 相乘,例如 2^4 = 2×2×2×2 = 16。范围的上限还要减 1,是因为这些组合从 0 开始编号,0 也占一种。后文用 10₂ 这样的下标表示二进制;没有下标时,按表头或上下文说明的进制读。
电路里,多位数据可以用多根线一起传,每根线负责一位。这组线合起来就是数据总线。
2. 二进制和十进制怎样互换?
二进制转十进制:把为 1 的位置加起来
例如 110101:
位权: 32 16 8 4 2 1
各位: 1 1 0 1 0 1
110101 = 32 + 16 + 4 + 1 = 53
为 0 的位置不贡献数值,可以直接跳过。
十进制转二进制:从大到小拆成位权
反过来把 53 写成二进制,就从不超过 53 的最大位权 32 开始。能减就写 1,不能减就写 0:
| 当前位权 | 剩下的数够不够减 | 这一位写什么 | 减完剩多少 |
|---|---|---|---|
| 32 | 53 够减 32 | 1 | 21 |
| 16 | 21 够减 16 | 1 | 5 |
| 8 | 5 不够减 8 | 0 | 5 |
| 4 | 5 够减 4 | 1 | 1 |
| 2 | 1 不够减 2 | 0 | 1 |
| 1 | 1 够减 1 | 1 | 0 |
从上往下读这一列,就得到 110101。中间不够减的位置也要写 0,否则后面各位的位置就变了。
十六进制可以把二进制写短一点:每四位对应一位十六进制。十六进制的 A~F 分别表示 10~15,例如 1111 写成 F;0011 1100 分成两组,分别是 3 和 12,因此写成 3C,表示十进制的 60。
3. 加法和减法:进位与借位
加法满二进一
最基本的规则是:
| 本位相加 | 二进制结果 | 本位留下什么 | 向高一位进多少 |
|---|---|---|---|
| 0 + 0 | 0 | 0 | 0 |
| 0 + 1,或 1 + 0 | 1 | 1 | 0 |
| 1 + 1 | 10 | 0 | 1 |
| 1 + 1 + 低位进来的 1 | 11 | 1 | 1 |
例如 0101 + 0011,也就是 5 加 3:
0101
+ 0011
------
1000
从右往左算:
- 最右边:
1 + 1 = 10,写 0,向左进 1。 - 第二位:
0 + 1 + 进位1 = 10,写 0,再向左进 1。 - 第三位:
1 + 0 + 进位1 = 10,写 0,再向左进 1。 - 最左边:
0 + 0 + 进位1 = 1,写 1。
得到 1000,就是 8。高一位要把低位送来的进位一起算进去。
减法不够减,就向高位借
二进制中,向左边借来的 1,在当前位相当于 2,也就是二进制的 10。所以本来不够算的 0 − 1,借位后就变成 10 − 1 = 1。
例如 0110 − 0011,也就是 6 减 3:
0110
- 0011
------
0011
最右边的 0 − 1 不够,向左借 1,本位得到 1;左边原来的 1 已经借走,变成了 0。第二位现在又是 0 − 1,再向更高位借 1,也得到 1。剩下的高两位都是 0 − 0,所以结果为 0011,就是 3。
如果左边也是 0,就需要继续向更高位借。例如 1000 − 0001,也就是 8 减 1。可以先把借位后的各列数量写出来,再逐列相减:
| 借位过程 | 权重 8 的列 | 权重 4 的列 | 权重 2 的列 | 权重 1 的列 |
|---|---|---|---|---|
| 原来的 1000 | 1 | 0 | 0 | 0 |
| 从权重 8 借一个到权重 4 | 0 | 2 | 0 | 0 |
| 从权重 4 再借一个到权重 2 | 0 | 1 | 2 | 0 |
| 从权重 2 再借一个到权重 1 | 0 | 1 | 1 | 2 |
| 各列减去 0001 | 0 | 1 | 1 | 1 |
表中的 2 是借位过程中暂时放在该列的数量,不是二进制数里允许写出的数字。每一步都只是换一种方式分配同一个 8,例如最后借完时 0×8 + 1×4 + 1×2 + 2×1 = 8。减去最低位的 1 后,得到 0111,也就是 7。
这里先讨论被减数不小于减数的情况。得到负数时该怎样表示,后面和补码一起讲。
4. 乘法和除法
乘法:每一位只有“加上”或“不加”
乘数的某一位是 0,这一行就是 0;是 1,就把被乘数写下来,再按这一位的位置向左错开。
例如 101 × 11,也就是 5 乘 3:
101
× 11
------
101 乘数最低位为 1:加上 101
+ 1010 高一位也为 1:把 101 左移一位再加
------
1111
这里相当于算 5×1 + 5×2 = 15,结果 1111 就是 15。
除法:够减一次就商 1,不够就商 0
和十进制竖式除法一样,从左往右读被除数,每次把下一位接到当前余数的右边,再判断够不够减去除数。
“接到右边”可以按一个具体动作来算:旧余数左移一位,再添入刚读到的位,也就是 当前值 = 旧余数 × 2 + 新读入的位。例如旧余数是 1,再读到一个 1,当前值就是二进制 11,数值为 3。
例如 1101 ÷ 11,也就是 13 除以 3。下表里的数都用二进制表示:
| 读到哪一位 | 当前拿来除的数 | 这一位的商 | 本步余数 |
|---|---|---|---|
| 第 1 位:1 | 1,不够减 11 | 0 | 1 |
| 第 2 位:1 | 11,够减 11 | 1 | 0 |
| 第 3 位:0 | 0,不够减 11 | 0 | 0 |
| 第 4 位:1 | 1,不够减 11 | 0 | 1 |
商按顺序是 0100,去掉前面的 0 就是 100,余数为 1。也就是商 4、余 1;用 4×3 + 1 = 13 可以核对。除数不能为 0。
为什么每一步只需要判断“能不能减一次”?上一步的余数一定小于除数;乘 2 再加上新读入的 0 或 1 后,当前值仍小于除数的两倍。所以本步的商只能是 0 或 1,减一次已经足够,不会需要写出商 2。
5. 按位运算、奇偶与计数
按位运算是把对应位置分别交给逻辑门。例如两组四位数据做 AND,得到的仍是四位:
0101
0011
----
0001 每一列分别做 AND
对同一对输入,几种运算的结果可以放在一起看:
| 对 0101 和 0011 做什么 | 结果 |
|---|---|
| 加法 | 1000 |
| 按位 AND | 0001 |
| 按位 OR | 0111 |
| 按位 XOR | 0110 |
加法会在位与位之间传递进位;按位 AND、OR、XOR 则是各个位置分别判断。因此 OR 与加法得到的结果可能不同。
把一组位用 XOR 合并,每一步的结果再与下一位做 XOR,最后就只留下一个 0 或 1。从 0 开始算,每遇到一个 1,结果就翻转一次;遇到 0 则不变。所以最后为 1,说明有奇数个 1;最后为 0,说明有偶数个 1。
用同一个 0101 来区分三件事:
| 要判断什么 | 答案 | 理由 |
|---|---|---|
| 这个数是不是奇数 | 是 | 它表示 5;最低位是 1,其余位置代表的都是偶数 |
| 里面有奇数个 1 吗 | 没有 | 一共有两个 1,所以各位 XOR 的结果是 0 |
| 里面一共有几个 1 | 两个 | 要输出数量 2,也就是二进制的 10 |
先弄清要求的是数值、奇偶判断,还是数量,才能知道应该输出几位、算什么。
6. 几个速算方法,以及位宽的限制
| 看到的形式 | 可以怎样读 | 例子 |
|---|---|---|
| 只有一个 1,后面全是 0 | 直接看这个 1 所在位置的位权 | 10000 是 16 |
| n 位全是 1 | 比下一个位权小 1,即 2^n−1 |
1111 = 16−1 = 15 |
| 最低位是 0 或 1 | 分别是偶数或奇数 | 1010 是偶数,1011 是奇数 |
| 左移一位,右边补 0 | 位数足够时相当于乘 2 | 0011 → 0110,即 3 → 6 |
| 右移一位,左边补 0 | 对无符号整数,相当于除以 2 后取整数部分 | 0111 → 0011,即 7 → 3,原最低位说明余数为 1 |
纸上计算时可以往左多写一位,固定宽度的电路则未必能保存多出来的那一位。例如:
1111 + 0001 = 1 0000
15 加 1 得到 16,需要五位才能表示。若输出只保留四位,就只剩下 0000;额外的进位要用另一条信号表示,才能保留这次计算的完整结果。四位的 1000 左移一位时也一样:完整结果是 10000,若只留四位就变成 0000。
因此,速算时除了看运算,还要看允许保留多少位。这一篇先把数算清楚;后面搭电路时,再把结果位、进位以及保存的位置一一对应起来。
7. 二进制交互速算
下面是一个二进制速算练习:点选 128 到 1 的位权,让它们加起来等于目标数。每点一下都会算出当前的和,并提示还差多少或多了多少;凑对以后,会显示目标数的二进制写法。「换一题」随机给出 1~255 之间的新目标,「清空」取消全部选择。
练习是网页上的交互组件,在网站版里可以直接操作;在 GitHub 上阅读时,这里只有这段说明。