--- title: "01-进制与进制转换" aliases: - 进制与进制转换 created: 2026-08-29 tags: - 基础与理论 - 信息的表示与处理 --- # 进制与进制转换 > 本篇是总篇 [[00-信息的表示与处理]] 2.2 节的展开:什么是进制、生活里到处都是进制、计算机里的 0b/0/0x,以及全套转换方法(简单拆分法 + 反复乘除法)。 ## 一、何为进制 进制也就是**进位计数制**的简称,是人为定义的带进位的计数方法。 对于任何一种进制——**X 进制**,表示每一个数位上的数,运算时都是逢 X 进一位。 例如:十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,八进制是逢八进一,以此类推,X 进制就是逢 X 进一。 ![[十进制进位-5ce3009a.gif]] ## 二、生活中的进制 进制根本不是计算机的发明,它早就长在生活里了。从定义出发逐个对号入座: | 进制 | 逢几进一 | 生活例子 | | --- | --- | --- | | 二进制 | 逢二进一 | 两只鞋子 = 1 双鞋;两个抓手 = 1 双手 | | 三进制 | 逢三进一 | 3 个月 = 1 个季度 | | 四进制 | 逢四进一 | 4 个季度 = 1 年 | | 七进制 | 逢七进一 | 7 天 = 1 周 | | 十进制 | 逢十进一 | 0、1、2、…、9、10、…(最常用) | | 十二进制 | 逢十二进一 | 12 瓶啤酒 = 1 打 | | 二十四进制 | 逢二十四进一 | 24 小时 = 1 天 | | 六十进制 | 逢六十进一 | 60 秒 = 1 分钟;60 分钟 = 1 小时 | 所以"进制"这个概念一点不玄——只是换一套"凑满就进位"的规则而已。 ## 三、计算机中的进制 计算机里常用的进制有三种,C 语言里各有各的前缀: ### 1、二进制:前缀 0b 以 0b 作为前缀,跟上 0 和 1 组成的数字: ``` #include int main() { int a = 0b101; printf("%d\n", a); return 0; } ``` 输出: > 5 因为 %d 代表输出的数是十进制,所以需要将二进制转换成十进制以后输出,0b101 在十进制下的值为 5。数字比较小时可以直接数出来:二进制下 101 对应十进制下的 5。 ### 2、八进制:前缀 0 讲八进制之前,先看一段代码: ``` #include int main() { int a = 0123; printf("%d\n", a); return 0; } ``` 那么,这段代码的输出值是多少呢? > 83 嗯,如果没学过 C 语言,肯定认为这是 123,实际上它的答案是 83。 为什么呢?参考二进制的表示法,八进制的表示法是前缀 1 个 0,然后跟上 0-7 的数字。换言之,我们需要把 123 这个**八进制数**转换成十进制后再输出,转换结果就是 83。这里数字较大,已经没法一个一个数出来了,所以需要进行进制转换——方法马上讲。 ### 3、十六进制:前缀 0x 以 0x 或者 0X 作为前缀,跟上 0-9、a-f、A-F 的数字,大小写字母含义相同,分别代表 10 到 15: ``` #include int main() { int a = 0X123; printf("%d\n", a); return 0; } ``` 输出的是 291。 所以这里就涉及到了十六进制转换成十进制,接下来看进制转换。 ## 四、进制转换 先解决 2、8、16 之间的互转(也就是开头引入的问题——把一连串 01 表示成较简短的串),再解决它们与十进制的互转。 ### 4.1 简单拆分法(2、8、16 互转) 能"拆"的前提是 2、8、16 存在特殊关系: - 2³ = 8 → **3 位二进制 = 1 位八进制** - 2⁴ = 16 → **4 位二进制 = 1 位十六进制** 所以这类转换可以直接靠记忆和推算。 #### 二进制与十六进制 把二进制数 4 位 4 位地拆分,用数字 0~9 以及字符 A~F 表示 16 个可能的值: ![[image-ae62d2ad.png]] 记住**A 是 1010、C 是 1100、F 是 1111、4 是 0100、8 是 1000**,其他的就能很容易推出来了——记住 A C F 对应的十进制值,剩下的 B D E 通过相对关系算出来就行。 **练一下就会了:** **16→2** 给出 0x173A4C: ``` 1 7 3 A 4 C 0001 0111 0011 1010 0100 1100 ``` **2→16** 反过来,给出 `11 1100 1010 1101 1011 0011`,从右到左划分、4 个 4 个一组,剩余的自动用 0 补齐: ``` 0011 1100 1010 1101 1011 0011 3 C A D B 3 ``` 再练俩: ``` 0x39A7F8 → 0011 1001 1010 1111 1000 (对照 A=1010、F=1111、8=1000 逐位展开) 10 0110 1110 0111 1011 0101 → 0010 0110 1110 0111 1011 0101 → 2 6 E 7 B 5 ``` **如果带小数** (注意:因为精度问题,结果不会是准确的,很少问这类问题;硬是碰到,可以用这种方式解决) 以小数点为中心,往左往右 4 个 4 个划分:整数部分最左边不满 4 位就补 0,小数部分最右边没满 4 位不用管。 比如 101.01011: ``` 划分成:0101 . 0101 1 就是: 5 . 5 1 ``` **16→2** 反过来:小数点右边的最后一个数得补前导 0: ``` 5 . 5 1 0101 0101 0001 = 0101.0101 1 ``` #### 二进制与八进制 把二进制数 3 位 3 位地拆分,用数字 0~7 表示 8 个可能的值,要记的东西也一样: ``` 0(000) 1(001) 2(010) 3(011) 4(100) 5(101) 6(110) 7(111) ``` **2→8** 对于二进制 1111000010,三个化作一组、左边补零: ``` 001 111 000 010 1 7 0 2 ``` **8→2** 对于八进制 423: ``` 4 2 3 100 010 011 ``` **若存在小数** 整数部分和小数部分分开:整数部分 3 位一组从右往左划分,不足 3 位左侧补 0;小数部分 3 位一组从左往右划分,不足 3 位右侧补 0。 (还是那句话,这玩意不准确,可以别管) #### 八进制与十六进制 八进制和十六进制之间没有直接通道,**以二进制为中介**: **16→8** F2C4B 先换成 2 进制再换成 8 进制: ``` 1111 0010 1100 0100 1011 ← 每位十六进制展开成 4 位二进制 011 110 010 110 001 001 011 ← 从右边开始三个化作一组,左边补零 3 6 2 6 1 1 3 ``` **8→16** 反过来:先把八进制换成对应的 2 进制,再以 4 位为一个单元做划分。 ### 4.2 反复乘除法(与十进制互转) 2、8、16 的相互转换解决了,接下来研究它们与十进制的转换。 先说结论:**X 进制转十进制、十进制转 X 进制,有着一套模板套路**。如果看不懂公式,后面有各个进制的实例。 #### 套路 **1、X 进制 → 十进制** 对 X 进制的数定义三个概念: - 【概念1】数字部分从右往左编号为 0 到 n,第 i 个数字位记作 di,这个数字就是 dn…d1d0 - 【概念2】每个数字位有一个权值 - 【概念3】第 i 个数字位的权值是 X^i 基于以上概念,**X 进制转十进制 = 每一位数字 × 它的权值,再累加**: ![[image-93f3bea6.png]] 举个例子,八进制数 0123 转十进制,套公式: ![[image-da6a25be.png]] - 1 权值最高,8 的 2 次 - 2 权值次之,8 的 1 次 - 3 权值最低,8 的 0 次 这样就把一个八进制的数转换成了十进制。为了加深印象,前文提到的十六进制数 0x123,套用同样的公式: ![[image-5560a5ee.png]] **2、十进制 → X 进制** > 从 X 进制转十进制的原理可知,任何一个十进制数字都是由 X 进制的幂的倍数累加而成。所以,一个数一定有 X⁰ 这部分,而这部分可以通过原数除以 X 的余数得到。然后把原数除以 X 后得到的商,肯定又有 X⁰ 的部分,就这样重复试除,直到商为 0 时结束——过程中的余数**逆序**一下,就是对应进制的数了。 对于整数部分采用**除 x 取余法**,对于小数部分采用**乘 x 取整法**: ![[image-18730e2f.png]] 如果看了这里还是不太理解,接下来把 2、8、16 分别与 10 进制的转换各过一遍。 #### 十六进制与十进制 **十六进制 → 十进制** 用相应的十六的幂乘以每个十六进制数字: ``` 7AF(H) = 1967(D) 从 F 开始右往左数:F 为第 0 位,A 为第 1 位,7 为第 2 位 7×16² + 10×16¹ + 15×16⁰ = 1792 + 160 + 15 = 1967 ``` (计算量很大,所以一般也看不见这种题,都是十进制换其他) **十进制 → 十六进制** 将十进制数字 x 转化为十六进制,可以反复用 x 除以 16,得到一个商 q 和一个余数 r: - 把余数 r 作为最低位数字,把商 q 继续参与循环运算 - x = q×16 + r - 反复对 q 执行该操作直到商为 0,其余数组合起来的数(**逆序**)就是转换结果 可以叫做**除 16 取余法**: ``` 314156(D) = 4CB2C(H) 314156 / 16 = 19634 …… 12 12 就是 C → C 19634 / 16 = 1227 …… 2 2 还是 2 → 2C 1227 / 16 = 76 …… 11 11 是 B → B2C 76 / 16 = 4 …… 12 12 是 C → CB2C 4 / 16 = 0 …… 4 4 还是 4 → 4CB2C ``` (这其实就是用一个栈去接收每轮余数,然后出栈即逆序输出——大一的时候也用程序实现过这一过程) **如果加上小数部分** 道理一样,把小数分成整数部分和小数部分。比如 58.75 转成十六进制: 整数部分的 58,还是**除 16 取余法**: ``` 58 / 16 = 3 …… 10 → A 3 / 16 = 0 …… 3 → 3A ``` 小数部分 0.75,用的是**乘 16 取整法**: ``` 0.75 × 16 = 12.00 → 3A.C 0.00 × 16 = 0.00 → 3A.C0 …… ``` 能观察到两个不同点: 1. 这回取下来的是**整数部分**,留下小数部分进入下一轮乘法运算 2. 这回取出的整数**不是逆序,而是顺序**地放在小数点后面 (这个例子不是很好看得出这一点,后面 10 换 2 能更直观看到) #### 十进制与二进制 **二进制 → 十进制** ``` 1111 1111 1×2⁷ + 1×2⁶ + 1×2⁵ + 1×2⁴ + 1×2³ + 1×2² + 1×2¹ + 1×2⁰ = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255 ``` ![[image-07420aec.png]] **十进制 → 二进制** 直接用带小数的例子:123.45,分为 123 和 0.45 两部分。 整数部分的 123,**除 2 取余法**: ``` 123 / 2 = 61 …… 1 1 61 / 2 = 30 …… 1 11 30 / 2 = 15 …… 0 011 15 / 2 = 7 …… 1 1011 7 / 2 = 3 …… 1 11011 3 / 2 = 1 …… 1 111011 1 / 2 = 0 …… 1 1111011 ``` 小数部分 0.45,**乘 2 取整法**: ``` 0.45 × 2 = 0.9 1111011.0 0.9 × 2 = 1.8 1111011.01 0.8 × 2 = 1.6 1111011.011 0.6 × 2 = 1.2 1111011.0111 0.2 × 2 = 0.4 1111011.01110 0.4 × 2 = 0.8 1111011.011100 ``` 还可以继续往下,反正就是**取整数部分往后加、取小数部分进入下一轮乘法运算**。 这里有个应试小技巧:题目里一般算完整数部分就能排除两个答案,再算小数,算到 4、5 位结果肯定就出来了。 ![[image-18730e2f.png]] 比如这道题:以下二进制的值与十进制 23.456 最接近的是? 10111.0101 / 11011.1111 / 11011.0111 / 10111.0111 23.456 可拆分为整数部分 = 23、小数部分 = 0.456。 整数部分用**除 2 取余法**: ``` 23 ÷ 2 = 11 余 1 1 11 ÷ 2 = 5 余 1 11 5 ÷ 2 = 2 余 1 111 2 ÷ 2 = 1 余 0 0111 1 ÷ 2 = 0 余 1 10111 ``` 逆序排列余数,整数部分的二进制为 **10111**——排除两个选项了。 小数部分用**乘 2 取整法**: ``` 0.456 × 2 = 0.912,取整数部分为 0 10111.0 0.912 × 2 = 1.824,取整数部分为 1 10111.01 0.824 × 2 = 1.648,取整数部分为 1 10111.011 0.648 × 2 = 1.296,取整数部分为 1 10111.0111 ``` 已经得到答案:**11011.0111**。 #### 八进制与十进制 **八进制 → 十进制** ``` 3725(O) = 3×8³ + 7×8² + 2×8¹ + 5×8⁰ = 1536 + 448 + 16 + 5 = 2005(D) ``` **十进制 → 八进制** 1770.625 换八进制: 整数部分,**除 8 取余法**: ``` 1770 ÷ 8 = 221 余 2 2 221 ÷ 8 = 27 余 5 52 27 ÷ 8 = 3 余 3 352 3 ÷ 8 = 0 余 3 3352 ``` 小数部分,**乘 8 取整法**: ``` 0.625 × 8 = 5.0 3352.5 0.0 × 8 = 0.0 3352.50 ``` ## 五、小结 整套东西收敛下来其实就四句话: | 方向 | 方法 | 关键点 | | --- | --- | --- | | 十进制 → 其他(整数部分) | 循环除以目标进制 | 余数**逆序**存放 | | 十进制 → 其他(小数部分) | 循环乘目标进制取整 | 整数部分**顺序**接在小数点后 | | 其他 → 十进制 | 按权展开再累加 | 个位权重为 0,往左递增、往右递减 | | 2、8、16 互转 | 以二进制为中介拆分 | 3 个 3 个划是八进制,4 个 4 个划是十六进制 | 这些过程都可以用程序去实现(循环加栈),现在就懒得做这意义不大的事情了——大一上学期写过,代码就不贴出来了,又臭又长(毕竟当时啥也不会)。 ## 六、盲点自测 1. **`int a = 0123;` 输出为什么不是 123?** ——前缀 0 是八进制标志,123 要按八进制转十进制输出,得 83。 2. **十六进制的 A~F 大小写有区别吗?** ——没有,0x2A 和 0x2a 相同。 3. **为什么 2、8、16 之间能直接"拆位"?** ——因为 8=2³、16=2⁴,3 位/4 位二进制恰好对应 1 位八/十六进制。 4. **十进制转 X 进制,为什么余数要逆序?** ——第一轮余数是最低位(X⁰ 那部分),越往后权重越高,所以最后算出的反而是最高位。 5. **小数部分为什么用乘法而不是除法?** ——小数是"负幂"部分,乘 X 把 X⁻¹ 顶成个位,顶出来的整数就是当前小数位的值;除法只会越除越小。 6. **带小数的进制转换结果为什么常常不精确?** ——乘法取整可能无限循环(比如 0.45 那串),二进制只能近似表示,这和总篇里"连续与离散"一节是同一件事。 --- 进制这一关过了,数字在计算机里的"写法"就没问题了。但正数好办,**负数怎么表示**才是重头戏 → [[02-原码反码补码]] ⬅️ [[00-信息的表示与处理|总篇]] 🏠 [[00-信息的表示与处理]] ➡️ [[02-原码反码补码]]