# “0”与“1”

“0”与“1”，两个平凡的数字，因为计算机的出现而熠熠生辉。“0”与“1”属于二进制，而计算机内部的奥秘便与二进制息息相关。

学习有关进制的知识，对进一步学习计算机的内部结构会起到很大的作用，同时也是理解红石电路计算机工作原理的基础。

为了适应知识背景薄弱的读者，此文相对于其它类似资料较为冗余，所以还请谅解！

### 目录

* [什么是进制](#shen-me-shi-jin-zhi)
* [二进制的计数原理与基本运算](#er-jin-zhi-de-ji-shu-yuan-li-yu-ji-ben-yun-suan)
* [二进制与十进制间的转换方法](#er-jin-zhi-yu-shi-jin-zhi-jian-de-zhuan-huan-fang-fa)
* [十六进制的计数原理与基本运算](#shi-liu-jin-zhi-de-ji-shu-yuan-li-yu-ji-ben-yun-suan)
* [十六进制与十进制及二进制间的转换方法](#shi-liu-jin-zhi-yu-shi-jin-zhi-ji-er-jin-zhi-jian-de-zhuan-huan-fang-fa)
* [任意进制的计数原理及表达方法与基本运算](#ren-yi-jin-zhi-de-ji-shu-yuan-li-ji-biao-da-fang-fa-yu-ji-ben-yun-suan)
* [任意进制间的转换方法](#ren-yi-jin-zhi-jian-de-zhuan-huan-fang-fa)

## 什么是进制

早在文明出现的时候，数字便开始在人类的活动中应用了。我们最熟悉的十进制，便是由我们的祖先平日里掰十个手指头，久而久之传承下来的。众所周知，十进制有由 0 至 9 共十个数字，逢十进一。进制，其实就是一套计数方法，其中规定了所含的每一个数字的确切含义，以及进位退位的条件与操作规则。

数字的编码，既可以像罗马数字那样以加法表达，也可以像阿拉伯数字那样以乘法表达。罗马数字以“Ⅰ”作 1 、以“Ⅴ”作 5 、以“Ⅹ”作 10 。要用罗马数字表达 12 ，就写成“Ⅻ”（ 10 + 1 + 1 ）；要用罗马数字表达 9 ，就写成“Ⅸ”（ 10 – 1 ） 。对于罗马数字来讲，某一个具体数字的最大等级数字（例如“Ⅴ”与“Ⅹ”比较，“Ⅹ”等级更高）左侧的数字总和与它相减，而其右侧的数字要与此数字相加。像罗马数字那样的编码方法，不仅要定义大量的字母对应各个数位（例如 1 、 5 、 10 、 50 、 100），而且遇到复杂的数字时会比阿拉伯数字更难表达。阿拉伯数字相对来讲就简单多了，例如 233 这个十进制数字实际上是 ![](https://miao.su/images/2018/10/16/151c01.gif) 的结果，它是由各个数位上的数字乘以十的不同次幂累加出来的。像阿拉伯数字这样以乘法对数字进行编码，不仅可以方便地表达大且复杂的数字，也方便了进行各种运算，更重要的是这样的编码方式容许了进制的存在。

进制究竟是什么呢？我们可以看到，十进制下的 233 可以被拆成 ![](https://miao.su/images/2018/10/16/1156672.gif) ，也就是 200 + 30 + 3 。2 为这个数字中百位上的数字，后续补的两个零用于占位，将原本在个位上的 2 提升到百位上去；第一个 3 也是一样的道理，后续补的一个零使这个 3 从各位提升到十位。上面式子中的 10 ，叫做这个进制的基数，正好对应着十进制。

接下来我们便要开始学习其它新的进制了，原理与刚刚十进制的示例有诸多相似之处。

## 二进制的计数原理与基本运算

前面我们提到，二进制仅有“0”和“1”两种数字。

二进制能表示什么呢？你可以用它表示逻辑上的真假。例如，红石电路的信号便可以用二进制来表示，“0”表示灭，“1”表示亮。

但二进制并非只是一位数字，它还允许表达除了“0”与“1”以外更大的数字，也就是可以有许多位数字。

有些人会开始疑惑，只有这两个数字的话，怎么才能表达比 1 更大的数字？

这就涉及到二进制的计数原理了。十进制规定逢十进一，而二进制则规定逢二进一。也就是说，当两个二进制数字进行加法操作时，各个位加到 2 时便要变成 0 并向前一位进 1。例如：

![](https://miao.su/images/2018/10/16/127d1a2.gif)

二进制的减法也与十进制类似，十进制的减法规则为借一当十，二进制的减法规则便为借一当二。例如：

![](https://miao.su/images/2018/10/16/13e5b25.gif)

二进制的乘法规则比十进制要简单得多。在十进制中，我们需要记忆九九乘法表才能进行任意位数乘法；在二进制中，由于只有两个数字，所以只有一一得一这一句口诀，其它情况均属于任意数字乘以 0 仍为 0 的情况。也就是：

![](https://miao.su/images/2018/10/16/14cb3a2.gif)

竖式也完全适用于二进制，四则运算的竖式操作流程与十进制大抵相同，且整数与小数均可使用：

![](https://miao.su/images/2018/10/02/1ea3df.gif)

![](https://miao.su/images/2018/10/02/2469d5.gif)

![](https://miao.su/images/2018/10/02/3b7b66.gif)

![](https://miao.su/images/2018/10/02/4daf7e.gif)

> （除法部分由于 LibreOffice 不支持除法专用竖式，只能以根号代替）

仅仅做到二进制的基本运算是不够的。在实际应用中，由于二进制运算过于繁琐，所以一般要将其转换为我们熟悉的十进制进行计算，计算完成后再转回二进制。这就需要学习有关这两个进制之间的互相转换的方法。

## 二进制与十进制间的转换方法

前面我们有提到，十进制下的数字可以被拆分为由各个数位上的数字乘以对应的 10 的不同次幂所得结果的累加之和。例如：

![](https://miao.su/images/2018/10/16/15e12c5.gif)

我们不难看出其中蕴藏的一点规律。个位上的数字对应着 0 次方，十位上的数字对应着 1 次方，而百位上的数字对应着 2 次方，依此类推。从个位开始的 0，每往上升一位数字便加 1。无论多少次方，它们的底数都为 10，也同样就是十进制的进制基数。

> ### 0 次幂与 1 次幂
>
> 对于任意数字的 0 次幂与 1 次幂，有两条不变的规则：
>
> * 除了 0 以外任何数字的 0 次方均为 1；0 的 0次方没有意义。
> * 除了 0 以外任何数字的 1 次方均为其本身。
>
>   例如：
>
> ![](https://miao.su/images/2018/10/16/183c6e4.gif)
>
> 0 的 0 次方没有意义是有确切原因的。我们规定，任意数字 x 的 0次方等于于 x ÷ x = 1，而当 x 为 0 时会导致除数也为 0 。在除法中，任何数字除以 0 都没有意义。

而要将二进制往十进制转换，我们也要将二进制数字拆开，然后乘以二进制的进制基数 2 的对应次幂。例如：

![](https://miao.su/images/2018/10/16/11b3ea1.gif)

我们成功地作了一次进制转换！原先为 10110101 的二进制数，转换成了十进制的 181。

这种方法同样适用于小数：

![](https://miao.su/images/2018/10/16/12bbbd8.gif)

> ### 负数次幂
>
> 可能有些人不明白负数次幂是什么意思。其实它就相当于：
>
> ![](https://miao.su/images/2018/10/16/23bbc5c.gif)
>
> 也就是把幂的级数负号去掉，再取其倒数。例如：
>
> ![](https://miao.su/images/2018/10/16/2435f51.gif)

要将十进制转换到二进制有一种最简单的方法，通过连续的除法取得一串连续的余数，将余数逐个拼凑起来即可得到结果。

为了做到连续整除与取余，我们需要先认识一种新的除法竖式列法——短除法：

![](https://miao.su/images/2018/10/16/31e4e5f.gif)

短除法十分适用于较小除数的连续除法，因为它连续向下书写的方式使得前一次的商立即就能用作后一次的被除数。这种除法竖式，在日常生活中主要用于快速分解一个数字为一系列它的因数，且做到所有的因数乘积即为被分解的数字。不过，在进制转换中，使用它更多是为能在连续的除法中取得连续的余数：

![](https://miao.su/images/2018/10/16/32f351f.gif)

将商与余数由下而上拼起来，即可得到转换结果:![](https://miao.su/images/2018/10/16/1184e0.gif)。

这样就得到了十进制数 181 的二进制形式。进行计算时，要不断地通过整除以二进制的基数 2 获取一系列的余数，直到商小于基数 2 为止。计算完毕后，最底层的商与右侧的余数由下而上按顺序拼接起来，得到转换得到的二进制数。

以短除法对十进制整数进行转换固然方便，但这种方法却不适用于十进制小数，因为短除法的其中一个必要条件是能够整除。

有人会问，我先通过乘以 10 的几次方将小数点后移，把它转换成整数再进行短除法转换，转换完成后再把小数点移动回去。这种方法是错误的！我们在右移小数点时，每移动一位乘以的是 10 的幂，但到后面左移小数点时除以的却是 2 的幂，最终根本得不到正确答案。

那我们如何去处理小数部分的转换呢？整数部分用除法，小数部分则是用乘法：

![](https://miao.su/images/2018/10/16/24521f.gif)

将一系列积的整数部分拼合起来，即可得到转换结果 0.010101。

每一次乘以 2 后，截取所得积的整数部分，向当前得到的二进制小数末尾追加，所得积的小数部分作为乘数提取出来继续下一轮运算，直到小数部分为 0 或二进制小数的位数达到要求即可停止转换。

> ### 定点数、浮点数与精度
>
> 在计算机术语中，定点数与浮点数其实都是小数，精度是指定点数、浮点数估计它的真实数值所能精确到的程度。
>
> 定点数与浮点数的区别只在于小数点的动态性。定点数将小数点的位置固定在某个位置，而浮点数则能够任意调整小数点的位置。
>
> 一般情况下，我们在日常使用计算机时使用的都是浮点数，因为它可以动态地兼容小数点后任意位数的数字。定点数主要用于表示金额，由于金额一般都固定小数点后两位为有效数字，因此计算机存储金额时使用定点数也不失为一种好且安全的选择。
>
> 我们谈论浮点数的精度时，其实就是在看这个浮点数能够精确到小数点后多少位。如果超出这个浮点数所能支持的精度范围，那这个浮点数很可能会表示一个错误的或无法使用的值。

虽然这些转换方法并不是唯一的，但这些方法却是执行效率最高、最容易理解的方法。

## 十六进制的计数原理与基本运算

十六进制是另一个在计算机学中常用的进制，因为它能很好地表达原本冗余且难分析的二进制序列。很多时候，我们在查看、操作二进制数据时，并不会去操纵那长度恐怖的“0”与“1”，而是使用简短的十六进制数字代替二进制进行查看。与此功能类似的还有八进制与四进制，但一般没人会去用它们，所以本节不会对这些进制多作赘述。

十六进制沿用了十进制的十个数字“0”到“9”，并在此基础上增加了“A”到“F”共六个字母，分别对应十进制数字“10”到“15”。十六进制数字“0”到“F”也同时分别对应着二进制数字“0000”到“1111”。例如：

![](https://miao.su/images/2018/10/16/3397c4.gif)

我们可以看到，一位十六进制数字正好对应着四位二进制数字。我们将在下一节中具体了解如何进行二进制与十六进制间的转换。

十六进制的加减法与其它进制的加减法规则大同小异，加法为逢“十六”进一，减法为借一当“十六”。例如：

![](https://miao.su/images/2018/10/16/4a5f06.gif)

十六进制的乘法有些特别，因为十进制的九九乘法表在这里不适用，必须使用属于十六进制的专属乘法表。这样的乘法表并不需要记忆，因为我们可以通过向十进制的转换间接进行运算：

![](https://miao.su/images/2018/10/16/559de8.gif)

除法也同样可以通过向十进制转换间接进行计算：

![](https://miao.su/images/2018/10/16/6d3822.gif)

具体的与十进制的转换方法将在下一节介绍。

十六进制也可以用竖式。如果你有一份十六进制的乘法表，你便可以像往常那样以竖式进行乘除法运算，只要多留意十六进制下加法的进位规则即可：

![](https://miao.su/images/2018/10/02/116bcb4.gif)

![](https://miao.su/images/2018/10/02/125fafe.gif)

![](https://miao.su/images/2018/10/02/13f7fee.gif)

![](https://miao.su/images/2018/10/02/14bd28e.gif)

> （除法部分由于 LibreOffice 不支持除法专用竖式，只能以根号代替）

## 十六进制与十进制及二进制间的转换方法

想将十六进制与二进制间互转是件很轻松的事，因为转换时只需要做到替换就够了：

| DEC | HEX  | DEC | HEX  | DEC | HEX  | DEC | HEX  |
| --- | ---- | --- | ---- | --- | ---- | --- | ---- |
| 0   | 0000 | 4   | 0100 | 8   | 1000 | C   | 1100 |
| 1   | 0001 | 5   | 0101 | 9   | 1001 | D   | 1101 |
| 2   | 0010 | 6   | 0110 | A   | 1010 | E   | 1110 |
| 3   | 0011 | 7   | 0111 | B   | 1011 | F   | 1111 |

四位二进制数字正好能构成共 16 种不同的组合，对应着十六进制的共 16 种数字。正因为这样，所以才可以直接通过简单的替换就可以进行进制转换。

将十六进制向二进制转换的例子：

![](https://miao.su/images/2018/10/16/21845a9.gif)

将二进制向十六进制转换的例子：

![](https://miao.su/images/2018/10/16/2221698.gif)

将十六进制数向十进制转换，与将二进制向十进制转换的流程很相似，仅需要换一下每一次幂的底数就可以了：

![](https://miao.su/images/2018/10/16/23e3ccb.gif)

需要注意的是，对于十进制中没有的“A”到“F”数字，要先将其转换为“10”到“15”这些十进制数字，然后才能继续进行计算。

要将十进制数转换为十六进制数，同样可以使用短除法，只要不断除以 16 取余即可：

![](https://miao.su/images/2018/10/16/24b445f.gif)

将商与余数由下而上拼起来，即可得到转换结果 91D16。

在整理余数的时候，如果遇到超过 9 的数字，一定要将其先转换为十六进制数字，然后才能进行拼接。

当然了，短除法仍不适用于十进制小数向十六进制小数转换。我们可以沿用之前十进制小数向二进制小数转换的方法，使用与短除法相反的乘法：

0.12345 × 16 = 1.9752 (1)

0.9752 × 16 = 15.6032 (F)

0.6032 × 16 = 9.6512 (9)

0.6512 × 16 = 10.4192 (A)

0.4192 × 16 = 6.1072 (6)

0.7072 × 16 = ……

将一系列积的整数部分拼合起来，即可得到转换结果 ≈ 0.1F9A6。

在这个例子中，出现了无限循环的情况。原本在十进制中是个有限位的小数，到十六进制那儿却变成了一个无限不循环的小数。遇到这种情况时，我们只能取小数点后几位，获取尽量接近于原来真实数值的估算值，没办法取得确切的有限小数。

也许你已经发现，不同的进制的转换方法都十分相似，在转换规则上其实是一致的。接下来的内容，我们将了解通用的进制表示方法、计算与转换规则。

## 任意进制的计数原理及表达方法与基本运算

根据之前的内容，我们可以总结出以下的一些基本规则：

* 任何一个进制都有一个基数 n，称为 n 进制。
* n进制有且只有 n 个数字，数字从 0 数到 n – 1。
* n 必须是大于 1 的整数。

第三条规则是基于第二条规则得来的，因为第二条规则提到了必须有数字。如果 n 小于 1，那这个进制就什么数字也没有了。

那 n 为 1 时的一进制是否存在呢？答案是否定的。通过第二条规则，我们可以得到“一进制”只有一个数字 0，但只用 0 根本不能拿来进行运算：

> 理想：0 + 0 = 00
>
> 现实：0 + 0 = 0

无论怎么去拼凑 0，最终结果仍然是 0，任何运算对 0 都不起作用。

透过之前十六进制的例子，我们可以发现，一旦 n 超过十，就需要拿新的符号来代表新的数字。如果 n 非常大，不断造数字符号的方法就没办法用了。为了能表达任意进制的数字，我们可以继续沿用十进制，对 n 进制的每一个数字从 0 开始按顺序编号，这些编号便代表对应的数字。例如：

![](https://miao.su/images/2018/10/16/413d202.gif)

右下角的数字是这个数字的进制基数 n，用于声明这是一个 n 进制的数字。每一个以十进制进行编号获得的“数字”，周围都要以一层小括号包裹，防止不同位的数字编号之间互相混淆。

对于 n 小于等于 10 的进制，因为不会出现以十进制编号混淆的情况，所以可以省略不必要的小括号：

![](https://miao.su/images/2018/10/16/42b3ffc.gif)

现在我们可以随心所欲地表达任意进制的数字了。

任何进制的加减法都遵循逢进借出的规则。进行加法时，总要保证当某一位的数字大于等于基数时，减去一次基数并向前一位进一；进行减法时，当所在位的被减数小于减数时，在这个位前的最近的一个能够借位的数位减一，再将借得的数位逐个向后分配，一直分配到发起借位的那个数位位为止。例如：

![](https://miao.su/images/2018/10/02/169919f.gif)

![](https://miao.su/images/2018/10/02/1760d46.gif)

如果减法借位时所借的位不是前一位数，而是更前面的数，那么在借位后分配数字时要注意分配的数字：

![](https://miao.su/images/2018/10/02/18022f5.gif)

![](https://miao.su/images/2018/10/02/1928d4d.gif)

在十进制的减法借位中，发起借位的那一位被分配了 10，其余的在被借位与发起借位之间的所有位（如果有）被分配了 9。同样的道理，在 n 进制的减法借位中，发起借位的分配 n，其余在此之间的位分配 n – 1。

不同的进制有着不同的乘法表，因此不同的进制进行乘除法也有很大差异。我们仍然可以选择通过十进制进行间接运算，不过我们也可以尝试制作一份除了十进制以外其它进制的乘法表。在这个例子中，我选用的是大家相对较熟悉的十六进制。

随便找两个十六进制数字相乘：

```
B × C
```

将各个数字转到十进制：

```
11 × 12
```

计算得到：

```
132
```

最后将其转换到十六进制：

```
84
```

于是最终我们就能确定，十六进制乘法表中的 B 与 C 相乘得到的结果是 84。

如果仅仅是一位数乘一位数，流程非常简单。但如果遇到多位数乘多位数，就需要做大量像这样别扭的乘法，这时候就不得不依赖于查表来提高效率了。

无论进制基数是大于十还是小于十，乘法表均与十进制的不同，并不是仅仅基数大于十的进制就与十进制不一样了。我们可以再看一个五进制的例子：

![](https://miao.su/images/2018/10/16/51877cb.gif)

我们可以看出，即使十进制下的 4 × 2 在五进制下表示仍然是 4 × 2，但最终运算结果仍然不同。

对于很大的其它进制的数字，直接对其进行乘除法运算是件很麻烦的事情。在下一节中，我们会了解任意进制与十进制之间的转换方法，用以制作任意进制的乘法表。

## 任意进制间的转换方法

任意进制间的转换，既可以使用十进制进行间接转换，也可以直接通过万能公式直接进行转换。无论使用什么方法，其本质上都利用了以乘法计数的特点。

间接办法很简单，因为使用了我们熟悉的十进制作为桥梁。具体的操作方法是，先将欲转换的数字向十进制转换，然后再将十进制数字转换到所需要转换到的进制。

为了将任意进制转换到十进制，我们需要先回头看下二进制与十六进制向十进制转换的例子：

![](https://miao.su/images/2018/10/16/613b5c6.gif)

![](https://miao.su/images/2018/10/16/23e3ccb.gif)

我们可以总结得到，由 n 进制向十进制的转换公式为：

![](https://miao.su/images/2018/10/16/547f283.gif)

> （表示范围的部分由于 LibreOffice 不支持区间，只能以传统描述的集合代替）
>
> ### 求和符号(∑)
>
> 求和符号(∑)用于将一批有一定规律的数逐个相加。这个符号是欧拉于 1755 年最先使用的。它的通用记法如下：
>
> ![](https://miao.su/images/2018/10/02/216d27e.gif)
>
> 其中，m 为下界(从哪开始加)，n 为上界(加到哪里)，i、m、n 均为整数。i 从 m 开始计数，不断加一直到加到 n 以后停止。k 是包含了 i 的表达式，每一次计数得到的 i 会代入 k 得到一个值，这个值会被加到之前累加的结果上(刚开始累加时累加结果的值为 0)。
>
> 例如，我想从 1 加到 10：
>
> ![](https://miao.su/images/2018/10/02/22345d1.gif)
>
> 或者，我想将不同下标的 a 逐个加起来：
>
> ![](https://miao.su/images/2018/10/02/233730d.gif)

而要想将十进制转换为其它进制，短除法与小数连续乘法取整的方法都仍然适用。例如，转换一个同时有整数部分和小数部分的十进制数到四进制数，我们可以将其拆成两部分分类讨论：

233.2333 = 233 + 0.2333

### \[ 整数部分 ]

![](https://miao.su/images/2018/10/02/2407abb.gif)

### \[ 小数部分 ]

0.2333 × 4 = 0.9332

0.9332 × 4 = 3.7328

0.7328 × 4 = 2.9312

0.9312 × 4 = 3.7248

0.7248 × 4 = 2.8992

0.8992 × 4 = 3.5968

0.5968 × 4 = …

得到 ≈ 0.032334。

整理，得：

![](https://miao.su/images/2018/10/16/71a30cd.gif)

要进行 n 进制的转换，只要将整数部分中的除数与小数部分中的基数乘数替换为 n 即可。

还有一种更为通用的方法，可以做到直接由一个进制转换到另一个进制。但这种方法同样比较费事，因为它用到了非十进制的乘法。它十分像之前我们从任意进制向十进制转换所用的公式，但右下角的基数却有所变化：

（5 进制转换到 12 进制）

![](https://miao.su/images/2018/10/16/813fbe0.gif)

（12 进制转换到 7 进制）

![](https://miao.su/images/2018/10/16/82c3db6.gif)

如果使用这种方法去转换，一定要小心对应的四则运算规则，尤其是乘法！

上面的例子进行到第二步的时候，必须先分别将不同的进制都转换到欲转换到的进制，保证两边进制统一后才能继续计算。无论原进制与欲转换到的进制谁大谁小，这种方法都可以使用。
