---
schema_version: "library-reader-v1"
source_type: "original_book"
title: "编码 隐匿在计算机软硬件背后的语言"
author: null
edition: null
publisher: null
year: null
isbn: "9787121181184"
library_document_id: "38270854-596f-4a26-8183-db8c968c6020"
source_sha256: "249786274d83909f0bddc34d77f81ed8f0b14d1eac8843c772a37a3ecbfe2cfc"
pdf_physical_page_count: 438
pdf_physical_page_index_base: 0
range_start: 170
range_count: 10
html_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020?start=170&limit=10"
markdown_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=170&limit=10"
previous_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=160&limit=10"
next_url: "https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=180&limit=10"
---

# 编码 隐匿在计算机软硬件背后的语言

> 页码规则：PDF_PHYSICAL_PAGE_INDEX 是从 0 开始的权威文件物理页索引；PRINTED_PAGE 是页面中识别出的印刷页码，可能为空，二者不得混用。

## PDF_PHYSICAL_PAGE_INDEX=170 | PRINTED_PAGE=154

如上图所示，这个开关向下断开时表示选择加法运算，反之向上接通则表示选择减法运算。此外，右侧的8个灯泡用于表示计算结果。这里，第9个灯泡表示“上溢/下溢”。这个灯泡表明了正在计算的数字是一个不能用8个灯泡来表示的数字。如果在加法中得到了大于255（上溢，overflow）或在减法中得到了负数（下溢，underflow）这个灯泡就会发光。当减数大于被减数的时候，就会得到一个负数。

加法器中新增的主要部分就是一个用来求8位二进制数对1补数的电路。之前提到，二进制数对1求补数相当于对其每位取反，因此我们计算8位二进制数补数的时候可以简单地应用8个反向器。

问题是，该电路只会对输入求反，而我们要的是一台既能做加法又能做减法的机器，因此就要求该电路当且仅当进行减法运算时才实现反转。电路可以改造为如下图所示。

标记为“取反”的信号将被输入到每一个异或门中。回想一下异或门的工作方式，如下表所示。

因此，如果“取反”信号是0，则8个异或门输出与输入相同。例如，如果输入是01100001，那么输出也为01100001。如果“取反”信号为1，则输出信号反置。例如，如果输入为01100001，输出则为10011110。

编码隐匿在计算机软硬件背后的语言

154

## PDF_PHYSICAL_PAGE_INDEX=171 | PRINTED_PAGE=155

将8个异或门合并起来画成一个器件，称为求补器（One's Complement)，如下所示。

将一个求补器，一个8位二进制加法器和一个异或门做如下连接。

注意，这里三个信号都标识为“SUB”，这就是加/减法转换开关。当该信号为0的时候，其进行的是加法运算，为1时进行的则是减法运算。在减法中，输入B(第二排开关）在送入加法器之前，需先通过求补电路进行取反。此外，在做减法时，我们通过设定CI（进位输入）为1来使得结果加1。而在加法中，求补电路将不起作用，且输入CI为0。

加法器的 SUB信号和CO（进位输出）输出作为异或门的输入来控制表示上溢/下溢的灯泡。如果SUB信号为0（表示进行加法运算)，则当加法器CO输出为1时灯亮，意思是加法计算结果大于255。

当进行减法运算的时候，如果减数（输入B）小于被减数（输入A），这时加法器的

13 如何实现减法

155

## PDF_PHYSICAL_PAGE_INDEX=172 | PRINTED_PAGE=156

CO输出为1。这表示减法的最后一步要减去100000000。也就是说减数要大于被减数，结果为负。上面所示器件现在还不能表示负数。因此，上溢/下溢指示灯仅在加法器的CO输出为0时才会亮起。

你应该会庆幸自己问了这个问题：“减法该如何实现呢？”

本章一直在谈论负数，但是并没有提到负数在二进制中是如何表示的。你可能设想二进制会同十进制一样应用传统的负数符号。例如，-77在二进制中为-1001101。这样做当然可以，但是应用二进制数的目的恰恰就在于只希望用0和1来表示所有的东西，当然也包括负号。

当然，你可以简单地用一个二进制位来表示负号，当这一位为1的时候就表示负数，为0则表示正数，尽管这样也是可行的，但还远远不够。还有另一种方法可以解决负数的表示间题，而且它还可以很轻松地让负数和正数相加。这种方法的缺点是必须提前算一下可能遇到的所有数字的位数。

让我们来想一想。通常用来表示正数和负数的方法，其好处就在于能表示所有的正数、负数。我们将0想象为这个无限延伸的序列的中点。这个序列中正数沿着一个方向延伸，而负数则按照另一个方向延伸：

…-1000000, -999999…-3, -2, -1, 0, 1, 2, 3…999999, 1000000…

但是，如果我们并不需要无限大或无限小的数，而且在开始的时候我们就可以预知所使用的数字的范围，那情况就有所不同了。

以支票账户为例，这里人们通常会遇到负数。假设我的账户余额不超过500美元，并且银行给了我们500美元的无息预支额度，意思就是账户余额数值应该是一个在499美元到-500美元之间的数。假设我们不会一次取出500美元，我们也不会支出多于500美元的金额，这里我们用到的只是美元，不涉及美分。

这些假设表明账户能处理的额度是介于-500到499之间，总共1000个数。这个约束说明只用三位十进制数，且不用负号就可以表示所有需要的数字。我们并不需要用到从500到999之间的正数，因为我们所需要的数的最大值为499。因此从500到999的三位数可以用来表示负数。具体情况如下：

编码—隐匿在计算机软硬件背后的语言

156

## PDF_PHYSICAL_PAGE_INDEX=173 | PRINTED_PAGE=157

用 500 表示-500  
用 501 表示-499  
用 502 表示-498  
用 998 表示-2  
用 999 表示-1  
用 000表示 0  
用001表示1  
用 002表示 2  
用497表示 497  
用498表示 498  
用 499表示 499

也就是说，以5，6，7，8或9开头的三位数实际上表示的都是负数，而不是把数字写成这样：

–500, –499, -498 … -4, -3, -2, -1, 0, 1, 2, 3, 4 … 497, 498, 499

用这种表示法，我们可以将它们写成：

500,501,502…996,997,998,999,000,001,002,003,004497,498,499

注意，这就形成了一个循环排序。最小的负数（500）看起来像是最大正数（499）的延续。而数字999（实际上是-1）是比0小1的第一个负数。如果我们在999上加1，通常会得到1000。由于我们处理的是三位数，这个结果实际上就是000。

这种标记方法称为10的补数(ten's complement)。为了将三位负数转化为10的补数，我们用999减去它再加1。也就是说，对10求补数就是对9的补数再加1。例如想要得到-255对10的补数，用999减去255得到744，然后再加1，得到745。

你可能听说过：“减一个数就等于加一个负数。”你可能会回答：“实际上还是减去了这个数。”然而，利用10的补数，我们将不会再用到减法。所有的步骤都用加法来进行。

13 如何实现减法

157

## PDF_PHYSICAL_PAGE_INDEX=174 | PRINTED_PAGE=158

假设你有一个余额为143美元的支票账户。你开了一张78美元的支票，也就意味着要将一个值为负的78美元加到143美元上。-78对10的补数为999-078+1，即922。因此新余额为143美元+922美元，相当于65美元（忽略溢出）。如果我们又开了一张150美元的支票，需要在余额上加上-150，-150对10求补数为850。因此先前的余额加上850等于915，就是新的账户余额。而这个余额实际上是-85美元。

这样的机制在二进制中被称为2的补数。以8位二进制数为例。范围为00000000\~11111111，对应十进制中的0\~255。但是如果你还想表示负数的话，则以1开头的每个8位数都表示一个负数，如下表所示。

现在所表示的数的范围是-128\~+127。最高有效位（最左位）作为符号位（signbit)。符号位中，1表示负数，0表示正数。

为了计算2的补数，则首先要计算1的补数，然后再加1。这等价于将每位取反再加1。例如，十进制数125写为二进制为01111101。为了表示-125的对2的补数，首先将01111101的每位取反，得到10000010，再加1，得到10000011。可以根据前面的表格核实一下结果。用同样的步骤，每位取反再加1，可以将数值还原。

编码—隐匿在计算机软硬件背后的语言

158

## PDF_PHYSICAL_PAGE_INDEX=175 | PRINTED_PAGE=159

这个系统为我们提供了一种不用负号就能表示正、负数的方法。同样也让我们自由地将正数和负数用加法法则相加。例如，将与-127和124等价的两个二进制数相加。利用上面的表格，可以简单地写为：

$$
\frac { 1 0 0 0 0 0 0 1 } { 1 1 1 1 1 0 0 }
$$

结果等于十进制的-3。

要注意的是，这里涉及了上溢和下溢情况，即结果大于127或小于-128。例如，将125与它自身相加：

$$
\textstyle \frac { 0 . 1 7 1 1 0 1 } { 1 . 1 1 1 0 1 }
$$

由于最高位为1，结果代表一个负数，相当于十进制的数-6。将-125与它本身相加也会出现同样的情况：

$$
\frac { 1 0 0 0 0 1 1 } { 1 0 0 0 0 0 1 1 0 }
$$

在一开始，我们规定所处理的数值为8位，因此最左位被忽略。右边8位相当于十进制的+6。

一般来说，如果两个操作数的符号相同，而结果的符号与操作数的符号不相同，这样的加法就是无效的。

现在，二进制数可以有两种不同的使用方法。二进制数可以是有符号的，也可以是无符号的。无符号的8位二进制数所表示的范围是0\~255。有符号的8位二进制表示的范围是-128\~127。无论是有符号的还是无符号的，数字本身是无法显示的。例如，如果有一个人问：“有一个8位二进制数，值为10110110。它相当于十进制的多少？”你必须先问：“它是有符号数还是无符号数？它可能为-74或者182。”

这就是二进制数的麻烦之处，它们只是一些0和1，本身并没有任何含义。

13 如何实现减法

159

## PDF_PHYSICAL_PAGE_INDEX=176 | PRINTED_PAGE=NOT_DETECTED

反馈与触发器

电可以让物体运动，这个道理人人都懂。只要稍微扫视一下我们的房间就会发现：很多电器中都装了电动机，比如钟表、电扇、食品加工器，以及CD播放器等。电同样可以使扩音器中的磁芯振动，正因为如此我们的音响设备、电视机才能够产生声音，播放语音和音乐。有一类设备或许能很清晰地阐释电能驱使物体运动的最简单也最具代表性的方式，然而币干这类设备正在被能够实现同样功能的电子器件逐步取代，它们正在迅速地消失。在我看来，最令人赞叹的例子应该算是电子蜂鸣器和电铃了。

将继电器、电池、开关按如下形式连接。

## PDF_PHYSICAL_PAGE_INDEX=177 | PRINTED_PAGE=161

如果你认为这个系统看起来有点古怪，说明你还没有发挥出想象力。或许以前我们没见过采用这种连接方式的继电器，因为我们通常所见过的继电器，其输入和输出是分开的，而这里却构成了一个回路。当开关闭合后，电路就连通了。

连通的电路使得电磁铁把金属簧片拉了下来。

当金属簧片的位置变化时，电路不再连通，电磁铁不再具有磁性，金属簧片又弹回原位。

14 反馈与触发器

161

## PDF_PHYSICAL_PAGE_INDEX=178 | PRINTED_PAGE=162

如此一来，电路又一次连通了。从整个过程来看：开关一旦闭合，金属簧片就会上下跳动电路也会随之连通或断开声音也就会随之发出。如果金属簧片发出了一种刺耳的声音，这套系统就成为了一个蜂鸣器。如果金属簧片前端是一把小锤子，旁边只要放上一个锣，就构成了一个电铃。

有两种方法可以使继电器连接成为一个蜂鸣器，下面再给出一种方法的描述，在示意图中包含了一个标准电压符号和一个接地符号。

看到这幅图你或许立刻想起了第11章介绍过的反向器，因此电路可以简化为如下图所示。

正如你所记得的那样，当反向器的输入是0的时候，它的输出就为1；而当其输入为1时，输出就为0。电路中的开关一旦闭合，反向器中的继电器就会在连通与断开这两种状态之间反复交替。你也可以将电路中的开关省去，这样就可以使反向器连续地工作，如下所示。

这幅图似乎在表达着一种矛盾的逻辑，反向器的输出与其输入是相反的，但是在这里，输出同时又是输入！然而，我们要牢牢记住，反向器在本质上就是一个继电器，而继电器将状态取反以得到另一个状态是需要一点点时间的。所以，即使输入和输出是相同的，输出也会很快地改变，成为输入的相反状态（当然，输出随即也会很快改变输入，

162

编码——隐匿在计算机软硬件背后的语言

## PDF_PHYSICAL_PAGE_INDEX=179 | PRINTED_PAGE=163

如此反复）。

电路的输出是什么呢？其实就是要么提供电压，要么不提供电压，在两者之间切换。我们也可以换种方式来表达——输出结果要么是0，要么是1。

我们把这种电路称为振荡器(oscillator)，它和我们先前学到的所有东西存在本质上的区别。在此之前我们讲过的所有的电路，其状态的改变都依靠人为的干预，通常是通过改变开关状态来实现的。但是振荡器却在不需要人干涉的情况下，可以完全自发地工作。

当然，单独的一个振荡器用处并不大，但是在本章的后面和接下来的几章里，我们会发现，在与其他电路连接后所组成的自动控制系统中，振荡器有着举足轻重的作用。为了使不同组件同步工作，所有计算机都配备着某种振荡器。

当采用0和1的交替序列来表示振荡器的输出时，我们一般使用下面这样的图来形象地描述输出。

我们可以通过这幅图来充分地了解电路的输出，水平坐标代表时间，垂直坐标用来表示输出是0还是1。

这幅图表示随着时间的推移，振荡器的输出在0和1之间按照固有的规律交替变化。正因为这一点，振荡器又经常被称为时钟(clock)，通过振荡进行计数也是一种计时方式。

振荡器运行速度究竟有多快呢？换句话说，金属簧片多久会振动一次？或者每秒钟振动多少次呢？这很大程度上依赖于继电器的内部构造。你容易想到，一个又大又重的继电器只能缓慢地上下摆动；而一个又小又轻的继电器却可以高速地跳动。

振荡器从某个初始状态开始，经过一段时间又回到先前初始状态的这一段间隔定义为振荡器的一个循环（cycle)，或者称为一个周期，如下图所示。

14 反馈与触发器

163

下一段：[继续读取](https://workshop.closeclaw.top/api/ai/read/38270854-596f-4a26-8183-db8c968c6020.md?start=180&limit=10)
