进制转换
一、课上练习
编程练习
二、知识总结
✨ 进制转换的核心思想
记数系统是一套用于表示和处理数字的符号规则体系,包括数字符号和记数规则。不同的文明和应用场景发展出了不同的记数系统。
✨ 进制的数字符号
数字符号有非常多的种类,数学和计算机中常用阿拉伯数字来进行记数。
其他常见数字符号如下:
- 阿拉伯数字:1、2、3、4、5、......
- 中文数字:一、二、三、四、五、......
- 罗马数字:I、II、III、IV、V、......
✨ 进位制
进位制是日常最常用的记数规则。n 进制就是使用 n 种数字符号来记数,满 n 进 1。
常见的进位制包括:
- 十进制:使用 0-9 这 10 种数字符号记数,满 10 进 1
- 二进制:使用 0-1 这 2 种数字符号记数,满 2 进 1
- 八进制:使用 0-7 这 8 种数字符号记数,满 8 进 1
- 十六进制:使用 0-9 和 A、B、C、D、E、F 这 16 种数字符号记数,满 16 进 1
除此之外,很多单位转换也利用了 n 进制的思想,例如:
- 5 个手指为 1 只手
- 60 秒为 1 分钟,60 分钟为 1 个小时
- 365 天为 1 年
- 1024B 为 1KB,1024KB 为 1MB,1024MB 为 1GB
✨ 十进制转 n 进制
十进制转 n 进制分为整数部分的转换和小数部分的转换。十进制转 n 进制的过程与十进制求每一位上的数是什么的过程极为相似,如果不记得具体的求解方式,可以通过考虑如何获得十进制各个位上的数来梳理思路。
整数部分转换
方法:除基取余,逆序排列
将十进制整数转换为 n 进制整数的步骤:
- 用十进制整数不断整除 n,记录每次的余数
- 重复直到商为 0
- 将记录的余数逆序排列,即得到 n 进制整数
示例1:将十进制数 456 转为二进制
从右到左依次求各位的值:
| 步骤 | 运算 | 余数 | 商 |
|---|---|---|---|
| 第1位 | 456 % 2 | 0 | 228 |
| 第2位 | 228 % 2 | 0 | 114 |
| 第3位 | 114 % 2 | 0 | 57 |
| 第4位 | 57 % 2 | 1 | 28 |
| 第5位 | 28 % 2 | 0 | 14 |
| 第6位 | 14 % 2 | 0 | 7 |
| 第7位 | 7 % 2 | 1 | 3 |
| 第8位 | 3 % 2 | 1 | 1 |
| 第9位 | 1 % 2 | 1 | 0 |
将余数从下往上排列,得到二进制整数为:111001000
示例2:将十进制数 100 转为八进制
| 步骤 | 运算 | 余数 | 商 |
|---|---|---|---|
| 第1步 | 100 / 8 = 12 余 4 | 4 | 12 |
| 第2步 | 12 / 8 = 1 余 4 | 4 | 1 |
| 第3步 | 1 / 8 = 0 余 1 | 1 | 0(结束) |
将余数从下往上排列,得到八进制数:144
验证:1 \ 8^2 + 4 \ 8^1 + 4 \* 8^0 = 64 + 32 + 4 = 100 ✓
示例3:将十进制数 255 分别转为二进制、八进制、十六进制
- 转二进制(除2取余):
- 255 / 2 = 127 余 1 → 127 / 2 = 63 余 1 → 63 / 2 = 31 余 1 → 31 / 2 = 15 余 1 → 15 / 2 = 7 余 1 → 7 / 2 = 3 余 1 → 3 / 2 = 1 余 1 → 1 / 2 = 0 余 1
- 结果:11111111(8 个 1)
- 转八进制(除8取余):
- 255 / 8 = 31 余 7 → 31 / 8 = 3 余 7 → 3 / 8 = 0 余 3
- 结果:377
- 转十六进制(除16取余):
- 255 / 16 = 15 余 15(F) → 15 / 16 = 0 余 15(F)
- 结果:FF
验证:11111111(2) = 377(8) = FF(16) = 255(10) ✓
小数部分转换
方法:乘基取整,正序排列
将十进制小数转化为 n 进制小数的步骤:
- 将十进制数的小数部分乘以 n,记录整数部分
- 取剩余小数部分继续乘以 n
- 重复直到小数部分变为 0(或达到精度要求)
- 将记录的整数部分正序排列,即得到 n 进制小数
注意:整数部分转换是"逆序",小数部分转换是"正序",两者方向相反,不要混淆!
示例1:将十进制数 0.625 转为二进制
从左到右依次求各位的值:
| 步骤 | 运算 | 整数部分 | 剩余小数 |
|---|---|---|---|
| 第1位 | 0.625 \* 2 = 1.25 | 1 | 0.25 |
| 第2位 | 0.25 \* 2 = 0.5 | 0 | 0.5 |
| 第3位 | 0.5 \* 2 = 1.0 | 1 | 0 |
二进制小数为:0.101
验证:1 \ 2^(-1) + 0 \ 2^(-2) + 1 \* 2^(-3) = 0.5 + 0 + 0.125 = 0.625 ✓
示例2:将十进制数 0.7 转为二进制(无限循环小数)
| 步骤 | 运算 | 整数部分 | 剩余小数 |
|---|---|---|---|
| 第1位 | 0.7 \* 2 = 1.4 | 1 | 0.4 |
| 第2位 | 0.4 \* 2 = 0.8 | 0 | 0.8 |
| 第3位 | 0.8 \* 2 = 1.6 | 1 | 0.6 |
| 第4位 | 0.6 \* 2 = 1.2 | 1 | 0.2 |
| 第5位 | 0.2 \* 2 = 0.4 | 0 | 0.4 |
| ... | 开始循环 | ... | ... |
二进制小数为:0.10110...(无限循环)
提示:并非所有十进制小数都能精确转换为 n 进制小数,可能产生无限循环小数。这也是计算机中浮点数精度问题的根源之一。
示例代码
下面的代码实现了十进制整数转任意 n 进制的功能:
1#include <bits/stdc++.h>
2using namespace std;
3
4string decimal2base(int decimal_number, int base) {
5 if (decimal_number == 0) {
6 return "0";
7 }
8 string base_number;
9 while (decimal_number) {
10 int value = decimal_number % base;
11 char digit;
12 if (value < 10) {
13 digit = value + '0';
14 } else {
15 digit = value - 10 + 'A';
16 }
17 base_number += digit;
18 decimal_number /= base;
19 }
20 int len = base_number.length();
21 for (int i = 0; i < len / 2; ++i) {
22 swap(base_number[i], base_number[len - 1 - i]);
23 }
24 return base_number;
25}
26
27int main() {
28 int n;
29 cin >> n;
30 for (int i = 0; i < n; ++i) {
31 int base, decimal_number;
32 cin >> base >> decimal_number;
33 string base_number = decimal2base(decimal_number, base);
34 cout << base_number << endl;
35 }
36 return 0;
37}✨ n 进制转十进制
n 进制转十进制只需要把 n 进制每一位的数值乘以该位的权重即可。权重就是 n 的对应次幂,类似于已知十进制数上的每一位求十进制数。
整数部分转换
方法:按权展开,逐位求和
将 n 进制整数转换为十进制整数的步骤:
- 从最右边一位开始,权重为 n^0 = 1
- 每一位的数值乘以对应权重(从右到左依次是 n^0、n^1、n^2...)
- 将所有结果求和,即得到十进制整数
示例1:二进制数 111001000 转十进制
| 位置(从右) | 数字 | 数值 | 权重 | 计算 |
|---|---|---|---|---|
| 第8位 | 1 | 1 | 2^8 = 256 | 1 \* 256 = 256 |
| 第7位 | 1 | 1 | 2^7 = 128 | 1 \* 128 = 128 |
| 第6位 | 1 | 1 | 2^6 = 64 | 1 \* 64 = 64 |
| 第5位 | 0 | 0 | 2^5 = 32 | 0 \* 32 = 0 |
| 第4位 | 0 | 0 | 2^4 = 16 | 0 \* 16 = 0 |
| 第3位 | 1 | 1 | 2^3 = 8 | 1 \* 8 = 8 |
| 第2位 | 0 | 0 | 2^2 = 4 | 0 \* 4 = 0 |
| 第1位 | 0 | 0 | 2^1 = 2 | 0 \* 2 = 0 |
| 第0位 | 0 | 0 | 2^0 = 1 | 0 \* 1 = 0 |
结果:256 + 128 + 64 + 8 = 456 ✓
示例2:十六进制数 2A3 转十进制
| 位置(从右) | 数字 | 数值 | 权重 | 计算 |
|---|---|---|---|---|
| 第2位 | 2 | 2 | 16^2 = 256 | 2 \* 256 = 512 |
| 第1位 | A | 10 | 16^1 = 16 | 10 \* 16 = 160 |
| 第0位 | 3 | 3 | 16^0 = 1 | 3 \* 1 = 3 |
结果:512 + 160 + 3 = 675 ✓
小数部分转换
方法:按权展开,逐位求和(负幂次)
将 n 进制小数转换为十进制小数的步骤:
- 从小数点后第一位开始,权重为 n^(-1)
- 每一位的数值乘以对应权重(从左到右依次是 n^(-1)、n^(-2)、n^(-3)...)
- 将所有结果求和,即得到十进制小数
示例:二进制小数 0.101 转十进制
| 位置(小数点后) | 数字 | 数值 | 权重 | 计算 |
|---|---|---|---|---|
| 第1位 | 1 | 1 | 2^(-1) = 0.5 | 1 \* 0.5 = 0.5 |
| 第2位 | 0 | 0 | 2^(-2) = 0.25 | 0 \* 0.25 = 0 |
| 第3位 | 1 | 1 | 2^(-3) = 0.125 | 1 \* 0.125 = 0.125 |
结果:0.5 + 0 + 0.125 = 0.625 ✓
秦九韶算法(Horner 法则)
在代码实现中,n 进制转十进制通常不需要逐位计算幂次,而是使用更高效的秦九韶算法。
核心公式:decimalnumber = decimalnumber * base + value
以十六进制 "2A3" 转十进制为例,跟踪执行过程:
| 步骤 | 当前字符 | value | decimal_number 计算过程 | decimal_number |
|---|---|---|---|---|
| 初始 | - | - | - | 0 |
| i=0 | '2' | 2 | 0 \* 16 + 2 | 2 |
| i=1 | 'A' | 10 | 2 \* 16 + 10 | 42 |
| i=2 | '3' | 3 | 42 \* 16 + 3 | 675 |
这种方法从高位到低位逐步累积,避免了计算幂次,非常巧妙高效。
原理说明: 以 "2A3" 为例,按权展开是 2 \ 16^2 + 10 \ 16^1 + 3 \ 16^0,通过提取公因式变形为 ((2) \ 16 + 10) \* 16 + 3,这正是代码的计算顺序。
示例代码
下面的代码实现了任意 n 进制整数转十进制的功能:
1#include <bits/stdc++.h>
2using namespace std;
3
4int base2decimal(string &base_number, int base) {
5 int decimal_number = 0;
6 int len = base_number.length();
7 for (int i = 0; i < len; ++i) {
8 char digit = base_number[i];
9 int value;
10 if (digit >= '0' && digit <= '9') {
11 value = digit - '0';
12 }
13 if (digit >= 'A' && digit <='Z') {
14 value = digit - 'A' + 10;
15 }
16 decimal_number = decimal_number * base + value;
17 }
18 return decimal_number;
19}
20
21int main() {
22 int n;
23 cin >> n;
24 for (int i = 0; i < n; ++i) {
25 int base;
26 string base_number;
27 cin >> base >> base_number;
28 int decimal_number = base2decimal(base_number, base);
29 cout << decimal_number << endl;
30 }
31 return 0;
32}✨ 进制转换的常见错误
- 余数排列顺序搞反:十进制转 n 进制时,余数应该从下往上(即逆序)排列,很多同学按计算顺序正序排列导致结果反了
- 十六进制字母处理错误:十六进制中 10-15 对应 A-F,编码时容易忘记处理字母,或者把
value - 10 + 'A'写错 - n 进制转十进制时权重算错:最右边一位的权重是 n^0 = 1,不是 n^1,从右往左依次是 n^0、n^1、n^2...
- 特殊值 0 没有处理:当输入为 0 时,除基取余的循环不会执行,需要特判直接输出 "0"
- 混淆整数部分和小数部分的转换方向:整数部分是"除基取余,逆序排列",小数部分是"乘基取整,正序排列",两者方向相反