进制转换上篇:位权展开与十进制互转
二进制、八进制、十进制和十六进制看似是四套不同的数字,实质上只是同一个数值的不同书写方式。掌握基数、数码和位权后,任意进制转十进制可以用位权展开,十进制整数转其他进制可以用除基取余,小数部分则使用乘基取整。本文从原理出发,完整推导整数、小数、混合数与任意进制转换,并解释有限精度和误差从何而来。
一、计算机为什么使用二进制
计算机中的数字、字母、图片和声音最终都要编码为二进制数据。使用二进制并不是因为人类更容易阅读 0 和 1,而是因为数字电路更容易可靠地区分两种状态,例如高电平与低电平、导通与截止。
需要区分两个概念:
- 数值数据 具有明确的算术意义,例如整数
86、小数3.14; - 非数值信息 也可以编码成二进制,例如字符
A、图片像素和机器指令,但对应的比特模式要按特定编码规则解释。
例如,二进制位模式 01000001:
- 按无符号整数解释是十进制
65; - 按 ASCII 字符编码解释是字母
A; - 它本身只是一组比特,真正含义取决于数据类型和使用场景。
因此,"计算机内部都是 0 和 1"不等于"所有 0 和 1 都是二进制整数"。
二、进位计数制的三个基本概念
1. 基数
基数表示一种进制可使用多少个不同的基本数码。基数为 r 的计数制称为 r 进制。
| 进制 | 基数 | 可用数码 | 进位规则 |
|---|---|---|---|
| 二进制 | 2 | 0、1 |
逢二进一 |
| 八进制 | 8 | 0~7 |
逢八进一 |
| 十进制 | 10 | 0~9 |
逢十进一 |
| 十六进制 | 16 | 0~9、A~F |
逢十六进一 |
十六进制中:
text
A = 10
B = 11
C = 12
D = 13
E = 14
F = 15
字母大小写不影响数值,A 与 a 都表示十进制 10。
2. 数码
数码是书写该进制时允许使用的基本符号。1021 是合法的三进制数,因为其中每一位都小于 3;1031 不是合法三进制数,因为数码 3 不属于三进制。
一般地,r 进制中每一位数码 d 必须满足:
text
0 <= d < r
因此:
- 二进制不允许出现
2; - 八进制不允许出现
8或9; - 十六进制不允许出现
G。
3. 位权
同一个数码放在不同位置,表示的值不同。位置对应的权值称为位权。
十进制 523 可以展开为:
text
523 = 5 x 10^2 + 2 x 10^1 + 3 x 10^0
从小数点向左,位权依次是:
text
... r^3, r^2, r^1, r^0
从小数点向右,位权依次是:
text
r^-1, r^-2, r^-3, ...
无论什么进制,都遵循同一套位置计数规则。
三、位置计数法的统一公式
一个带小数部分的 r 进制数可以写成:
text
(d_n d_(n-1) ... d_1 d_0 . d_(-1) d_(-2) ... d_(-m))_r
其数值为:
text
d_n x r^n + ... + d_1 x r^1 + d_0 x r^0
+ d_(-1) x r^-1 + d_(-2) x r^-2 + ... + d_(-m) x r^-m
下标 _r 用来说明原数的进制。例如:
text
(1011)_2
(526)_8
(523)_10
(5A3B)_16
如果上下文容易混淆,必须标明进制。单独写 10 时,它可能表示:
text
(10)_2 = 2
(10)_8 = 8
(10)_10 = 10
(10)_16 = 16
数码串相同,基数不同,实际数值也不同。
四、其他进制转十进制:位权展开法
转换原则是:
- 从小数点开始确定每一位的指数;
- 用每位数码乘以对应位权;
- 把所有结果相加。
1. 二进制整数转十进制
以课堂笔记中的 (1010110)_2 为例:
text
(1010110)_2
= 1 x 2^6 + 0 x 2^5 + 1 x 2^4 + 0 x 2^3
+ 1 x 2^2 + 1 x 2^1 + 0 x 2^0
= 64 + 0 + 16 + 0 + 4 + 2 + 0
= 86
所以:
text
(1010110)_2 = (86)_10
初学者最容易把最右侧位的指数写成 1。实际上个位的位权始终是 r^0 = 1。
2. 八进制整数转十进制
text
(526)_8
= 5 x 8^2 + 2 x 8^1 + 6 x 8^0
= 5 x 64 + 2 x 8 + 6 x 1
= 320 + 16 + 6
= 342
所以:
text
(526)_8 = (342)_10
3. 十六进制整数转十进制
text
(5A3B)_16
= 5 x 16^3 + 10 x 16^2 + 3 x 16^1 + 11 x 16^0
= 5 x 4096 + 10 x 256 + 3 x 16 + 11
= 20480 + 2560 + 48 + 11
= 23099
因此:
text
(5A3B)_16 = (23099)_10
处理十六进制时,要先把 A~F 换成 10~15,再参与位权计算。
五、带小数部分的位权展开
1. 二进制小数
text
(101.101)_2
= 1 x 2^2 + 0 x 2^1 + 1 x 2^0
+ 1 x 2^-1 + 0 x 2^-2 + 1 x 2^-3
= 4 + 0 + 1 + 0.5 + 0 + 0.125
= 5.625
所以:
text
(101.101)_2 = (5.625)_10
二进制小数点后的位权依次为:
text
2^-1 = 1/2
2^-2 = 1/4
2^-3 = 1/8
2^-4 = 1/16
2. 八进制小数
text
(17.34)_8
= 1 x 8^1 + 7 x 8^0 + 3 x 8^-1 + 4 x 8^-2
= 8 + 7 + 3/8 + 4/64
= 15.4375
3. 十六进制小数
text
(A.C)_16
= 10 x 16^0 + 12 x 16^-1
= 10 + 12/16
= 10.75
小数点左侧最靠近小数点的一位是 r^0,右侧最靠近小数点的一位是 r^-1,两边不要同时从 0 开始。
六、用霍纳法减少乘方计算
直接位权展开容易理解,但位数很多时反复计算幂比较繁琐。整数部分可以使用霍纳法从左到右累计:
text
当前结果 = 当前结果 x 基数 + 下一位数码
仍以 (5A3B)_16 为例:
text
初始结果 = 0
读入 5: 0 x 16 + 5 = 5
读入 A: 5 x 16 + 10 = 90
读入 3:90 x 16 + 3 = 1443
读入 B:1443 x 16 + 11 = 23099
写成嵌套形式:
text
(((5 x 16 + 10) x 16 + 3) x 16 + 11) = 23099
程序把字符串形式的进制数转换成整数时,经常使用这一思路,因为它只需要按顺序扫描数码并持续执行乘加操作。
七、十进制整数转其他进制:除基取余法
转换十进制非负整数 N 到 r 进制:
- 用
N除以基数r; - 记录余数,它是结果当前的最低位;
- 用商继续除以
r; - 直到商为 0;
- 把余数从后向前倒序排列。
之所以要倒序,是因为第一次得到的是最低位,最后一次得到的是最高位。
1. 十进制 86 转二进制
text
86 / 2 = 43 ... 0
43 / 2 = 21 ... 1
21 / 2 = 10 ... 1
10 / 2 = 5 ... 0
5 / 2 = 2 ... 1
2 / 2 = 1 ... 0
1 / 2 = 0 ... 1
余数按产生顺序是 0 1 1 0 1 0 1,逆序得到:
text
(86)_10 = (1010110)_2
反向检查:
text
2^6 + 2^4 + 2^2 + 2^1
= 64 + 16 + 4 + 2
= 86
2. 十进制 86 转八进制
text
86 / 8 = 10 ... 6
10 / 8 = 1 ... 2
1 / 8 = 0 ... 1
倒序读取余数:
text
(86)_10 = (126)_8
验证:
text
1 x 8^2 + 2 x 8^1 + 6 x 8^0
= 64 + 16 + 6
= 86
3. 十进制 86 转十六进制
text
86 / 16 = 5 ... 6
5 / 16 = 0 ... 5
因此:
text
(86)_10 = (56)_16
4. 转换到超过十进制的任意基数
当余数大于 9 时,要用约定字母表示。例如十进制 250 转十六进制:
text
250 / 16 = 15 ... 10
15 / 16 = 0 ... 15
余数 10 写作 A,余数 15 写作 F,逆序得到:
text
(250)_10 = (FA)_16
5. 十进制 0 是特殊情况
如果算法一开始就判断"商为 0"并直接结束,可能得不到任何数码。数学上:
text
(0)_10 = (0)_r
程序实现时必须单独处理输入为 0 的情况。
八、十进制小数转其他进制:乘基取整法
对 0 <= F < 1 的十进制小数,转换到 r 进制:
- 用小数部分乘以
r; - 取乘积的整数部分,作为结果当前最高位;
- 保留乘积的小数部分,再乘以
r; - 小数部分变为 0 时结束,或达到规定精度时停止;
- 整数部分按产生顺序从前向后排列,不需要倒序。
1. 十进制 0.625 转二进制
text
0.625 x 2 = 1.250 -> 取整数 1,保留 0.250
0.250 x 2 = 0.500 -> 取整数 0,保留 0.500
0.500 x 2 = 1.000 -> 取整数 1,保留 0
所以:
text
(0.625)_10 = (0.101)_2
验证:
text
1 x 2^-1 + 0 x 2^-2 + 1 x 2^-3
= 1/2 + 1/8
= 0.625
2. 十进制 0.6875 转十六进制
text
0.6875 x 16 = 11.0
整数部分 11 对应十六进制 B,小数部分已经为 0:
text
(0.6875)_10 = (0.B)_16
3. 整数与小数分别转换
转换十进制 86.625 到二进制时:
- 整数部分
86使用除 2 取余,得到1010110; - 小数部分
0.625使用乘 2 取整,得到.101; - 最后在小数点处合并。
结果是:
text
(86.625)_10 = (1010110.101)_2
口诀可以概括为:
text
整数:除基取余,余数倒排
小数:乘基取整,整数顺排
九、为什么有些小数无法有限表示
十进制 0.1 看起来很简单,但转为二进制时会无限循环:
text
(0.1)_10 = (0.00011001100110011...)_2
这是进制表示的性质,不是转换算法出错。类似地,十进制中的 1/3 只能写成循环小数 0.333...。
1. 有限表示的判断条件
把一个有理数的小数部分写成最简分数 p/q。它能在基数 r 中有限表示,当且仅当分母 q 的所有质因数都包含在基数 r 的质因数中。
- 十进制基数
10 = 2 x 5,所以最简分母只含 2 和 5 时可以有限表示; - 二进制基数
2,所以最简分母必须只含因数 2; - 八进制
8 = 2^3,条件与二进制相同; - 十六进制
16 = 2^4,条件也与二进制相同。
0.1 = 1/10 的分母包含因数 5,所以无法用有限二进制位精确表示;0.625 = 5/8 的分母只有因数 2,因此可以有限表示。
2. 截断与舍入
如果只保留 0.1 的前 12 个二进制小数位:
text
(0.000110011001)_2 = (0.099853515625)_10
它比真实的 0.1 略小。根据下一位进行舍入后可能得到:
text
(0.000110011010)_2 = (0.10009765625)_10
它又比真实的 0.1 略大。计算机浮点数只能使用有限位数,因此通常保存最接近的可表示值。这就是很多浮点计算出现微小误差的根源。
十、如何设置小数转换精度
乘基取整法必须有明确的停止条件:
- 小数部分准确变为 0;
- 已生成规定数量的目标进制小数位;
- 余数状态重复,可以确认进入循环。
如果要求保留 n 位目标进制小数,应至少再计算一位,用于决定是否舍入。例如保留 4 位二进制小数,需要观察第 5 位。
目标进制中保留 n 位后,相邻可表示数之间的间隔是:
text
r^-n
直接截断的绝对误差小于这个间隔;按最接近值舍入时,误差通常不超过半个间隔,但具体边界还取决于舍入规则。
十一、负数怎样做数学进制转换
如果讨论的是数学上的进制表示,可以先转换绝对值,再保留负号。例如:
text
(-86)_10 = -(86)_10 = (-1010110)_2
这里的负号是数学符号,不占数码位。
计算机内存中通常不会额外保存一个字符 -,而是用固定宽度的补码表示有符号整数。例如 -86 的 8 位、16 位和 32 位补码写法不同。补码属于"固定宽度下的数据编码",不能与普通的负号表示混为一谈,将在下一篇详细说明。
十二、任意进制之间的通用转换
将 p 进制转为 q 进制,通用路线是:
text
p 进制 --位权展开--> 十进制 --除基/乘基--> q 进制
例如把 (10201)_3 转为五进制。
先转十进制:
text
(10201)_3
= 1 x 3^4 + 0 x 3^3 + 2 x 3^2 + 0 x 3^1 + 1 x 3^0
= 81 + 18 + 1
= 100
再把 100 转为五进制:
text
100 / 5 = 20 ... 0
20 / 5 = 4 ... 0
4 / 5 = 0 ... 4
逆序读取:
text
(10201)_3 = (100)_10 = (400)_5
这种方法适用于任意合理基数。对于二、八、十六进制,由于基数之间是 2 的幂关系,可以直接按 3 位或 4 位二进制分组,下一篇会系统介绍这种更快的方法。
十三、转换结果如何自检
1. 反向位权展开
得到目标进制结果后,重新展开为十进制,看是否回到原值。例如:
text
(126)_8 = 1 x 64 + 2 x 8 + 6 = 86
2. 检查数码是否合法
目标是八进制却出现 8,目标是二进制却出现 2,说明结果一定错误。
3. 检查数量级
因为:
text
2^6 = 64 <= 86 < 128 = 2^7
所以 86 的二进制应有 7 个有效整数位。若结果只有 5 位或有 10 位,应重新检查。
4. 检查最低位
二进制整数的最低位可以反映奇偶性:
- 偶数最低位是
0; - 奇数最低位是
1。
86 是偶数,因此结果 1010110 以 0 结尾,符合预期。
5. 保留精确分数
处理小数时,尽可能用分数验证。例如:
text
(0.101)_2 = 1/2 + 1/8 = 5/8 = 0.625
分数不会引入十进制浮点近似,更适合检查转换过程。
十四、常见错误
1. 把余数按原顺序写出
除基取余第一次得到最低位,必须逆序排列。乘基取整第一次得到最高小数位,必须顺序排列。两种算法方向恰好相反。
2. 位权从 1 次方开始
小数点左侧最靠近小数点的一位是 r^0,不是 r^1。
3. 忘记十六进制字母的数值
A~F 不是变量,分别代表 10~15。(10)_16 是十进制 16,不是十进制 10。
4. 把小数点后的数码当十进制小数
(0.1)_2 表示 1 x 2^-1 = 0.5,不是十进制 0.1;(0.1)_16 表示 1/16 = 0.0625。
5. 认为所有小数都能转换到"余数为 0"
某些小数在目标进制中无限循环,必须按精度停止并说明是近似值。
6. 转换过程中太早使用近似十进制
任意进制小数经过十进制中转时,如果提前舍入,最终结果可能继续放大误差。能用精确分数或直接分组时,应优先保留精确形式。
十五、总结
所有位置进制都遵循同一条规律:每一位的实际贡献等于"数码乘以基数的对应次幂"。因此,其他进制转十进制使用位权展开;十进制整数转其他进制使用除基取余并逆序;十进制小数转其他进制使用乘基取整并顺序记录。
真正需要理解的难点有两个:第一,整数和小数两种转换算法的记录方向不同;第二,有限十进制小数不一定能用有限二进制小数表示。掌握这些原理后,不仅能手工转换,也能理解计算机浮点误差、整数编码和 C 语言进制输出的基础。