# 进制转换

## 定义

一个 $r$ 进制数（$r\ge 2$，$r$ 为整数）把数值写成数码串，每一位的数码取自 $0,1,\dots,r-1$，数码所在的位置决定它的**位权**。若整数部分有 $n$ 位、小数部分有 $m$ 位，则

$$
(b_{n-1}\cdots b_1b_0.b_{-1}\cdots b_{-m})_r
=\sum_{i=-m}^{n-1}b_i r^i .
$$

第 $i$ 位的位权是 $r^i$：小数点左边从最低位起依次为 $r^0,r^1,r^2,\dots$，小数点右边依次为 $r^{-1},r^{-2},\dots$。最高位记作MSB，最低位记作LSB。

下标 $r$ 标注底数，十进制常省略下标。不写下标时，同一串数码可能对应不同的值：

$$
(1011)_2=11,\qquad (1011)_8=521,\qquad (1011)_{16}=4113 .
$$

| 进制   | 底数   | 可用数码                                 |
| :--- | :--- | :----------------------------------- |
| 二进制  | $2$  | $0,1$                                |
| 八进制  | $8$  | $0\sim 7$                            |
| 十进制  | $10$ | $0\sim 9$                            |
| 十六进制 | $16$ | $0\sim 9$，$A\sim F$ 分别表示 $10\sim 15$ |

## 性质

### 位权与移位

相邻两位的位权之比等于底数 $r$。对整数表示来说，在末尾补一个 $0$，数值乘以 $r$；删去末尾的一个 $0$，数值除以 $r$。二进制整数左移一位就是乘 $2$，右移一位就是除以 $2$ 取整。

### 整数部分与小数部分分别处理

按权展开式中，整数部分只出现非负幂，小数部分只出现负幂，两者互不影响。所以含小数点的数可以整数、小数分别转换，再按小数点位置拼接。

### 以 $2$ 的幂为底的进制

若 $r=2^k$，则每一位 $r$ 进制数码恰好对应 $k$ 位二进制数。因为 $8=2^3$、$16=2^4$，二进制与八进制、十六进制可以按 $3$ 位或 $4$ 位分组直接互转。这种逐位对应只存在于底数为 $2$ 的幂的进制之间，十进制与八进制、十六进制之间没有。

## 转换方法

### $r$ 进制转十进制

按位权展开，把每一位的数码乘以该位的位权后相加。整数部分用正幂，小数部分用负幂。

### 十进制整数转 $r$ 进制

1. **除以 $r$，记下余数**

   用当前整数除以 $r$，余数就是当前最低位的数码。

2. **用商继续**

   把商作为新的整数，重复上一步，直到商为 $0$。

3. **逆序读取余数**

   最先得到的余数是最低位，最后得到的是最高位，从下往上读。

### 十进制小数转 $r$ 进制

1. **乘以 $r$**

   用当前小数部分乘以 $r$。

2. **取出整数部分**

   乘积的整数部分是当前小数位的数码，剩下的小数部分进入下一轮。十六进制中整数部分 $10\sim 15$ 要写成 $A\sim F$。

3. **重复到终止**

   直到小数部分变为 $0$，或达到所需精度。取出的数码按先后顺序从左到右写出。

> [!TIP]
> 大多数十进制小数不能写成有限位的二进制小数。例如 $0.1$ 的二进制展开是 $0.0001100110011\ldots_2$，乘 $2$ 的过程不会终止；此时只能按精度截断，截断会带来舍入误差。

### 二进制与八进制、十六进制互转

以小数点为界，整数部分向左、小数部分向右分组：八进制每 $3$ 位一组，十六进制每 $4$ 位一组，每组换成对应的 $1$ 位八进制或十六进制数码。反方向转换时，把每一位八进制数码写成 $3$ 位二进制数、每一位十六进制数码写成 $4$ 位二进制数，再以小数点为界拼接，去掉整数部分的前导 $0$ 和小数部分的末尾 $0$。

> [!WARNING]
> **补 $0$ 的方向**
>
> 不足一组时，整数部分在左端补 $0$，小数部分在右端补 $0$。补 $0$ 只影响分组，不改变数值；把方向弄反会直接算错。

八进制与十六进制的小数同样按位权展开，权分别为 $8^{-1},8^{-2},\dots$ 与 $16^{-1},16^{-2},\dots$。它们与十进制小数互转时，通常先换成二进制，再用分组法处理。

### 数位对应表

| 十进制 |  二进制 | 八进制 | 十六进制 |
| :-: | :--: | :-: | :--: |
|  0  | 0000 |  0  |   0  |
|  1  | 0001 |  1  |   1  |
|  2  | 0010 |  2  |   2  |
|  3  | 0011 |  3  |   3  |
|  4  | 0100 |  4  |   4  |
|  5  | 0101 |  5  |   5  |
|  6  | 0110 |  6  |   6  |
|  7  | 0111 |  7  |   7  |
|  8  | 1000 |  10 |   8  |
|  9  | 1001 |  11 |   9  |
|  10 | 1010 |  12 |   A  |
|  11 | 1011 |  13 |   B  |
|  12 | 1100 |  14 |   C  |
|  13 | 1101 |  15 |   D  |
|  14 | 1110 |  16 |   E  |
|  15 | 1111 |  17 |   F  |

## 例子

**把 $(1101)_2$ 转换为十进制**

$$
1\times 2^3+1\times 2^2+0\times 2^1+1\times 2^0
=8+4+0+1=13 .
$$

**把 $13$ 转换为二进制**

用 $13$ 反复除以 $2$，记录余数：

$$
\begin{array}{r|rr}
2 & 13 & \dots 1 \\ \hline
2 & 6 & \dots 0 \\ \hline
2 & 3 & \dots 1 \\ \hline
2 & 1 & \dots 1 \\ \hline
& 0 &
\end{array}
$$

从下往上读余数，得到 $(13)_{10}=(1101)_2$。

**把 $12.625$ 转换为二进制**

整数部分与小数部分分别转换，再拼接。

1. 整数部分 $12$：按除 $2$ 取余，$12=1100_2$。
2. 小数部分 $0.625$：

   * $0.625\times 2=1.25$，取整 $1$，剩 $0.25$；
   * $0.25\times 2=0.5$，取整 $0$，剩 $0.5$；
   * $0.5\times 2=1.0$，取整 $1$，剩 $0$，结束。

   按取出的顺序排列，$0.625=0.101_2$。

最终结果：$(12.625)_{10}=(1100.101)_2$。

**把 $(1101.101)_2$ 转换为十进制**

$$
\begin{aligned}
(1101.101)_2
&=\underbrace{1\times 2^3+1\times 2^2+0\times 2^1+1\times 2^0}_{\text{整数部分}}
+\underbrace{1\times 2^{-1}+0\times 2^{-2}+1\times 2^{-3}}_{\text{小数部分}} \\
&=(8+4+0+1)+(0.5+0+0.125) \\
&=13+0.625=13.625 .
\end{aligned}
$$

**把 $(110101)_2$ 转换为八进制**

以小数点为界，向左每 $3$ 位一组：

$$
(110101)_2 \rightarrow (110,\ 101)_2 \rightarrow (6,\ 5)_8 \rightarrow (65)_8 .
$$

**把 $(11101011)_2$ 转换为十六进制**

以小数点为界，向左每 $4$ 位一组：

$$
(11101011)_2 \rightarrow (1110,\ 1011)_2 \rightarrow (E,\ B)_{16} \rightarrow (EB)_{16} .
$$

## 编程实现

下面的两个程序分别实现二进制转十进制和十进制整数转二进制。它们把上面的步骤写成循环：前者用不断折半的位权代替重复的指数运算，后者把余数插到列表头部来完成逆序。

### 二进制转十进制

```python title="二进制转十进制"
b = input("Please enter a binary number:")
d = 0
weight = 2 ** (len(b) - 1)
for i in range(0, len(b)):
    if b[i] == "1":
        d = d + weight
    weight = weight // 2
    # ‘//’是整数除法
print(d)
```

如果每轮都在循环里重新计算 $2^k$，会多做大量乘法。改进后的写法只在循环外算一次最高位权 $2^{n-1}$，之后用整数除法把位权逐位折半，和按权展开的结果一致。注意只有数码为`1`的位需要累加。

### 十进制整数转二进制

```python title="十进制整数转二进制"
x = int(input("Please enter a decimal number:"))
r = 0
Rs = []
while x != 0:
    r = x % 2
    x = x // 2
    Rs = [r] + Rs
for i in range(0, len(Rs)):
    # 从最高位到最低位依次输出；Rs[0] 是最高位。
    print(Rs[i], end="")
```

`x % 2`依次给出从低到高的各位，`Rs = [r] + Rs`把新余数放到列表头部，因此最后从`Rs[0]` 开始输出就是高位到低位，不需要再反转列表。

## 交互探索

选择整数和进制，比较数位、位权与表示结果。

交互演示：同一个整数怎样用不同进制表示？。改变整数和进制，查看各数位及其位权。 请在站点操作演示。

## 练习

1. $(1101.101)_2$ 的十进制值是 \_\_\_\_\_\_（value）。

   答案：13.625

   $8+4+0+1+0.5+0+0.125=13.625$。

2. 十进制数 $25$ 的二进制表示是 \_\_\_\_\_\_（bits）（只填数码，不写下标）。

   答案：11001

   $25=16+8+1=2^4+2^3+2^0$，所以是 $11001_2$。

3. 八进制数 $(65)_8$ 的十进制值是 \_\_\_\_\_\_（value）。

   答案：53

   $6\times 8^1+5\times 8^0=48+5=53$。

4. 把 $(11101011)_2$ 写成十六进制，结果是：

   * A.  $(EB)_{16}$
   * B.  $(BE)_{16}$
   * C.  $(EA)_{16}$
   * D.  $(E1B)_{16}$

   答案：A

   每 $4$ 位一组：$1110=E$，$1011=B$，所以是 $(EB)_{16}$。

5. 下列关于进制转换的说法，正确的是：

   * A.  十进制小数 $0.1$ 的二进制表示是有限位的
   * B.  十进制整数转二进制时，最先得到的余数是最高位
   * C.  二进制转八进制时，以小数点为界每 $3$ 位一组，每组对应 $1$ 位八进制数
   * D.  二进制转八进制时，小数部分不足一组应在左端补 $0$

   答案：C

   $0.1$ 的二进制展开无限循环；除 $2$ 取余时最先得到的是最低位；小数部分应在右端补 $0$。
