NOI C++ 课程体系 / 数学基础篇 / 第1讲
CSP-J 入门级 · 数学基础 · m6-01

数及其运算
与进制转换

从自然数到实数,从十进制到二进制——掌握计算机世界的"数字语言",避开整数运算的隐形陷阱!

6 大模块 4 种进制 3 大转换法 5 大易错点 12 道分层练习
本讲 · 第 51/56 讲数及其运算与进制转换
模块 01

情景导航

用故事建立直觉,用类比理解抽象概念。

故事莱布尼茨与"0和1"的宇宙

17世纪,德国数学家莱布尼茨(Gottfried Wilhelm Leibniz)发明了现代二进制系统。

他坚信:"万物皆数",而所有数都可以仅用 0 和 1 两个符号来表示。

这个看似简单的想法,在200年后成为了整个计算机科学的基石!

今天,你手机里的每一张照片、每一段视频、每一条消息,在最底层都只是无数个 0 和 1 的组合。

但人类习惯使用十进制(因为有10根手指),计算机却只认识二进制。如何在两种"语言"之间自由翻译?这就是本讲要解决的核心问题。

①生活类比:进制无处不在
⏰

时间 = 60进制

1小时=60分钟,1分钟=60秒。满60进1。

→ 非十进制的日常例子
📅

星期 = 7进制

周一到周日,满7天回到周一。

→ 模运算的生活原型
👆

手指 = 10进制

人类有10根手指,所以发明了十进制。

→ 进制源于计数工具
💡

开关 = 2进制

灯只有"开"和"关"两种状态,天然对应0和1。

→ 计算机选择二进制的物理原因
②编程定义:数的家族谱系

数是信息学竞赛中最基本的操作对象。不同种类的数,在C++中有不同的表示方式和运算规则。

📌 本讲核心知识地图

板块核心内容对应大纲
数的分类自然数、整数、有理数、实数数及其运算
算术运算加减乘除、整除、取余、溢出数及其运算
进制概念基数、位权、数码进制与进制转换
通用转换R↔10 按权展开法 / 除基取余法进制与进制转换
特殊转换二↔八↔十六 分组对应法进制与进制转换
③核心口诀
🔑 口诀一:R进制转十进制

按权展开求和,每位乘以基数的幂
从右往左数,最低位是零次方

🔑 口诀二:十进制转R进制

除基取余倒排,商为零时停下来
先得余数是低位,后得余数高位摆

🔑 口诀三:二八十六互转

三位一组转八进,四位一组转十六
从小数点向两边分,不足补零凑整齐

模块 02

知识探究

系统掌握数的分类、运算规则与进制转换三大核心板块。

1数的分类 基础
类别符号定义C++类型示例
自然数ℕ非负整数(含0)unsigned int0, 1, 2, 3...
整数ℤ正整数、零、负整数int / long long-3, 0, 42
有理数ℚ能表示为 p/q (q≠0) 的数double (近似)1/3, -0.5, 7
实数ℝ有理数 + 无理数double / long doubleπ, √2, 3.14
⚠️ C++中的关键区别
  • int / int = 整除(截断小数)
  • int / double = 浮点除法
  • % 只能用于整数
  • double 有精度误差,不能直接判等
💡 竞赛注意事项
  • 数据范围决定类型选择
  • 10⁹ 以内用 int
  • 10¹⁸ 以内用 long long
  • 高精度需要自己实现或用Python
2算术运算与陷阱 高频考点
运算符名称规则⚠️ 陷阱
+加法正常相加可能溢出
-减法正常相减无符号数下溢变巨大正数
*乘法正常相乘最容易溢出!先判断再乘
/除法整数÷整数=整除7/2=3,不是3.5!
%取余a % b = a - (a/b)*b负数取余结果符号同被除数
⚠️

负数取余的坑

C++ 规则
-7 % 3 = -1(不是2!)
7 % -3 = 1
余数符号 = 被除数的符号
✅ 安全写法
若需非负余数:
((a % b) + b) % b
保证结果 ∈ [0, b-1]
🔑 整数运算三原则

① 整除截断:7/2=3,-7/2=-3(向零取整)
② 溢出致命:int 最大约 2×10⁹,两数相乘极易超界 → 用 long long
③ 负余同号:-7%3=-1,想要正余数需额外处理

3进制基本概念

R进制:逢R进1,每一位上的数码范围是 0 ~ R-1。

进制基数(R)可用数码前缀/后缀特点
二进制20, 10b / B计算机内部存储
八进制80~70 / OLinux文件权限
十进制100~9无人类日常使用
十六进制160~9, A~F0x / H颜色代码、内存地址

💡 核心概念:位权

任何一个R进制数都可以写成按权展开式:

(dₙdₙ₋₁...d₁d₀)ᵣ = dₙ×Rⁿ + dₙ₋₁×Rⁿ⁻¹ + ... + d₁×R¹ + d₀×R⁰

例如:(1A3)₁₆ = 1×16² + 10×16¹ + 3×16⁰ = 256 + 160 + 3 = 419

4R进制 → 十进制:按权展开法

方法:将每一位数码乘以对应的位权(R的幂次),然后求和。

🖐 手推示例1:(1011)₂ → (?)₁₀

1
标定位权(从右往左,0开始)
位权:2³   2²   2¹   2⁰
数码: 1    0    1    1
2
按权展开求和
= 1×8 + 0×4 + 1×2 + 1×1
= 8 + 0 + 2 + 1 = 11

🖐 手推示例2:(2F)₁₆ → (?)₁₀

1
F = 15,标定位权
位权:16¹   16⁰
数码: 2     F(15)
2
按权展开
= 2×16 + 15×1 = 32 + 15 = 47
C++R进制字符串转十进制
int toDecimal(string s, int base) {
    int result = 0;
    for (char c : s) {
        int digit;
        if (c >= '0' && c <= '9') digit = c - '0';
        else digit = c - 'A' + 10;  // A=10, B=11...
        result = result * base + digit;  // 秦九韶算法优化
    }
    return result;
}
💡 秦九韶算法:避免计算幂次,每次 result × base + digit,效率更高且不易溢出。
5十进制 → R进制:除基取余法

方法:不断除以R取余数,直到商为0,然后将余数逆序排列。

🖐 手推示例:(25)₁₀ → (?)₂

步骤除法商余数说明
①25 ÷ 2121最低位(LSB)
②12 ÷ 260
③6 ÷ 230
④3 ÷ 211
⑤1 ÷ 201最高位(MSB)

逆序读取余数:11001,即 (25)₁₀ = (11001)₂ ✅

C++十进制转R进制
string toBase(int n, int base) {
    if (n == 0) return "0";
    string result = "";
    while (n > 0) {
        int r = n % base;
        char c = (r < 10) ? ('0' + r) : ('A' + r - 10);
        result = c + result;  // 余数插到前面
        n /= base;
    }
    return result;
}
6二 ↔ 八 ↔ 十六 快速互转 必考

因为 8 = 2³,16 = 2⁴,所以可以直接分组对应,无需经过十进制中转!

🔄 二进制 ↔ 八进制
  • 每3位二进制 = 1位八进制
  • 从右向左分组,不足3位左边补0
  • 例:110 101 → 65₈
  • 反向:每位八进制展开为3位二进制
🔄 二进制 ↔ 十六进制
  • 每4位二进制 = 1位十六进制
  • 从右向左分组,不足4位左边补0
  • 例:0011 1010 → 3A₁₆
  • 反向:每位十六进制展开为4位二进制

🖐 手推:(11010110)₂ → (?)₈ → (?)₁₆

1
转八进制(3位一组)
分组:011 010 110(左边补1个0)
对应: 3     2     6 → (326)₈
2
转十六进制(4位一组)
分组:1101 0110
对应: D      6 → (D6)₁₆
🔑 常用二进制对照表(背下来!)

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

模块 03

实践应用

通过详细的手推追踪,巩固运算规则和转换方法。

题1整数除法与取余陷阱

题目:在C++中,以下表达式的值分别是多少?

(A) 17 / 5
(B) 17 % 5
(C) -17 / 5
(D) -17 % 5
(E) 17 / 5 * 5 + 17 % 5

查看参考答案
A
17 / 5 = 3
整数除法,向零截断,不是3.4
B
17 % 5 = 2
17 = 3×5 + 2
C
-17 / 5 = -3
向零取整,不是-4!
D
-17 % 5 = -2
余数符号同被除数:-17 = (-3)×5 + (-2)
E
17/5*5 + 17%5 = 3*5+2 = 17
恒等式:a/b*b + a%b == a 始终成立
题2进制转换综合

题目:将十六进制数 (3B7)₁₆ 分别转换为二进制、八进制和十进制。

查看参考答案

① 转二进制(每位展开4位):

3 → 0011,B → 1011,7 → 0111

结果:(1110110111)₂

② 转八进制(从二进制3位一组):

分组:001 110 110 111

对应:1   6   6   7

结果:(1667)₈

③ 转十进制(按权展开):

3×16² + 11×16¹ + 7×16⁰

= 3×256 + 11×16 + 7

= 768 + 176 + 7 = 951

题3位权展开法速算

题目:不写完整过程,快速心算 (10000)₂ 等于十进制多少?

查看参考答案

技巧:二进制中,1后面跟n个0 = 2ⁿ

(10000)₂ 中1后面有4个0 → 2⁴ = 16

同理:(100000000)₂ = 2⁸ = 256

(1111)₂ = 2⁴ - 1 = 15(全1 = 2ⁿ-1)

模块 04

总结提升

1核心要点总结
知识板块必须记住的核心
数的分类ℕ⊂ℤ⊂ℚ⊂ℝ;C++中int整除、double有精度误差
整除与取余向零截断;负余同号;a/b*b+a%b==a
R→10按权展开求和(秦九韶算法优化)
10→R除基取余,逆序排列
2↔8↔163位一组转八,4位一组转十六,无需经十进制
溢出防范乘法前先判断;大范围用long long
2拓展:负数的二进制表示(补码)

💡 计算机如何存储负数?

计算机使用补码(Two's Complement)表示负数:

① 正数的补码 = 原码本身

② 负数的补码 = 绝对值的二进制取反+1

例:-5 的8位补码 → 5=(00000101)₂ → 取反=11111010 → +1 = 11111011

好处:加减法统一处理,无需单独的减法电路!

3思考与启发

💡 学完本讲,你应该掌握的5项能力

1. 区分自然数、整数、有理数、实数,知道C++中对应类型。

2. 熟练进行四种进制之间的任意转换。

3. 掌握按权展开法和除基取余法两大通用算法。

4. 能用分组法快速完成二↔八↔十六互转。

5. 警惕整除截断、负数取余、溢出三大运算陷阱。

模块 05

学习检测

5 个初赛高频易错点,每个都有口诀帮你记住。

易错1

整数除法当成浮点除法

❌ 错误
7 / 2 = 3.5
C++中两个int相除结果是int!
✅ 正确
7 / 2 = 3(截断)
要得到3.5需写 7.0 / 2 或 (double)7 / 2
口诀:整除整还是整,想要小数转浮点!
易错2

负数取余结果搞错符号

❌ 错误
-7 % 3 = 2
这是Python的规则,不是C++!
✅ 正确
-7 % 3 = -1
C++余数符号 = 被除数符号
口诀:余数跟着被除走,正被正余负被负!
易错3

除基取余忘记逆序

❌ 错误
25转二进制:余数1,0,0,1,1
正序读 → 10011 = 19 ❌
✅ 正确
余数逆序读 → 11001 = 25 ✅
先得的是最低位!
口诀:除基取余倒着排,先低后高别搞反!
易错4

二八十六互转分组方向错

❌ 错误
从左往右分组
→ 高位组可能不满,导致错位
✅ 正确
从右往左分组(从小数点向两边)
高位不足补零在左边
口诀:分组从右往左数,左边补零右边齐!
易错5

十六进制字母大小写混淆

❌ 错误
认为 A 和 a 在不同语境下值不同
或忘记 A=10, F=15
✅ 正确
A~F = 10~15,大小写等价
背熟对照表是基本功!
口诀:A十B十一C十二,D十三E十四F十五!
速查进制转换速查表
转换方向方法关键步骤
R → 10按权展开法每位 × R^位序,求和
10 → R除基取余法反复÷R取余,逆序排列
2 → 83位分组每3位二进制 = 1位八进制
2 → 164位分组每4位二进制 = 1位十六进制
8 → 2逐位展开每位八进制 → 3位二进制
16 → 2逐位展开每位十六进制 → 4位二进制
8 ↔ 16以2为桥先转二进制,再转目标进制
📌知识点笔记卡

🔢 数的家族

ℕ ⊂ ℤ ⊂ ℚ ⊂ ℝ

C++: int / long long / double

int/int = 整除(截断)

⚠️ 运算陷阱

整除截断:7/2=3

负余同号:-7%3=-1

溢出:乘法先用long long

📐 R→10

按权展开求和

秦九韶:result = result×R + d

从高位到低位迭代

📐 10→R

除基取余,逆序排列

先得低位,后得高位

商为0时停止

🔄 快速互转

2↔8:3位一组

2↔16:4位一组

从右向左分组,左补零

💾 补码

正数:原码本身

负数:取反+1

统一加减法运算

课后练习

分层练习作业

A基础 / B提高 / C挑战,难度递增,点击展开答案与解析。

🟢 A组 · 基础(4题)

A1选择题

将二进制数 (1101)₂ 转换为十进制,结果是()

A. 11   B. 13   C. 15   D. 12

查看参考答案

答案:B. 13

1×8 + 1×4 + 0×2 + 1×1 = 8+4+0+1 = 13

考点:二进制转十进制的通用方法是「按位权展开求和」:从右往左第 i 位(i 从 0 起)的权是 2i,把该位数字乘上权再累加。

易错:从左往右把最高位当成 20 起算,或直接把 1101 看成 1+1+0+1=3。

A2选择题

十进制数 58 转换为八进制是()

A. (72)₈   B. (62)₈   C. (74)₈   D. (58)₈

查看参考答案

答案:A. (72)₈

58÷8=7余2,7÷8=0余7 → 逆序:72

考点:十进制转 R 进制的通用方法是「除 R 取余、结果逆序」,本题 R=8,每步留下的是低位。

易错:把余数按计算先后顺序正着连起来写成 27,忘记最后要逆序。

A3选择题

在C++中,表达式 -11 % 4 的值是()

A. 1   B. -3   C. 3   D. -1

查看参考答案

答案:B. -3

-11 ÷ 4 = -2(向零截断),余数 = -11 - (-2)×4 = -11+8 = -3

考点:C++ 的整数除法是「向零截断」,取模满足恒等式 a % b = a - (a / b) * b,因此余数的符号跟随被除数 a。

易错:套用数学上「余数非负」的模定义算成 1,或与 Python 中 -11 % 4 == 1 的结果混淆。

A4选择题

下列哪个数不属于有理数?()

A. 0.333...   B. √2   C. -7/3   D. 0

查看参考答案

答案:B. √2

√2 是无理数,不能表示为两个整数之比。0.333...=1/3是有理数。

考点:有理数就是能写成两个整数之比的数(含整数、有限小数、无限循环小数),只有无限不循环小数才是无理数。

易错:看到 0.333... 后面带省略号就误判为无理数,其实它是循环小数、等于 1/3。

🟡 B组 · 提高(4题)

B1选择题

十六进制数 (2AF)₁₆ 对应的二进制数是()

A. 1010101111   B. 001010101111   C. 101010111100   D. 001010101110

查看参考答案

答案:B

2→0010, A→1010, F→1111 → 001010101111

考点:十六进制与二进制可以「一位换四位」直接转换:每个十六进制数字对应恰好 4 位二进制,逐位展开后拼接即可。

易错:把 A 记成 1011、F 记成 1110,或每位只写 3 位二进制导致位数错位。

B2填空题

将 (356)₈ 转换为十六进制,结果是(______)₁₆

查看参考答案
C++完整参考代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "356";              // 待转换的八进制数
    int k = 8;                     // 原进制
    const char* digit = "0123456789ABCDEF";   // 16 进制数字表,10~15 写作 A~F

    // 第一步:k 进制 → 十进制,按权展开
    int dec = 0;
    for (char c : s)
        dec = dec * k + (c - '0');       // 每读一位就3
    cout << "(" << s << ")" << k << " = (" << dec << ")10" << endl;

    // 第二步:十进制 → 十六进制,8
    char buf[64];                  // 从低位到高位临时存放每一位
    int len = 0;
    int tmp = dec;
    while (tmp > 0) {
        buf[len++] = digit[tmp % 16];    // 取最低位
        tmp /= 16;                       // 去掉最低位
    }
    cout << "(";
    for (int i = len - 1; i >= 0; i--)   // 倒序输出,先出来的低位移到最后
        cout << buf[i];
    cout << ")16" << endl;               // 输出 (EE)16
    return 0;
}

答案:(356)8 = (EE)16。

思路:两种做法都对。① 快速法(八、十六都是二的幂):八进制 1 位拆 3 位二进制,3→011、5→101、6→110,拼成 011101110;再从右往左每 4 位分一组:1110 1110 → E E。② 通用法(程序里的做法):先按权展开把八进制变成十进制——(356)8 = 3×64+5×8+6 = 238,再用"除 16 取余、结果倒序"把 238 变成十六进制,238÷16 余 14(E) → 商 14 ÷16 余 14(E) → 倒着读就是 EE。

考点:任意进制转换的两条路:k 进制 → 十进制用"按权展开",十进制 → k 进制用"while 取余 + 数组倒序输出";以及 2/8/16 之间"3 位 / 4 位分组"的快捷法。

易错:① 分组一定要从右往左,左边不够就补 0(这里刚好 9 位,补 1 个 0 成 011101110);② 十六进制里 10~15 要写成 A~F,别直接写成 10、11;③ 别忘了倒序——除基取余先得到的是最低位。

B3选择题

若 a = 17, b = 5,则 a - a/b*b 等价于()

A. a / b   B. a % b   C. b % a   D. a * b

查看参考答案

答案:B. a % b

由恒等式 a/b*b + a%b == a,可得 a - a/b*b == a%b

考点:本质是整数除法的恒等式 a = (a / b) * b + a % b,移项即得 a - a / b * b 就是 a 除以 b 的余数。

易错:以为 a / b * b 等于 a 而选 0,忽略了整数除法会把余数直接舍掉。

B4选择题

一个8位二进制数能表示的最大无符号整数是()

A. 128   B. 255   C. 256   D. 127

查看参考答案

答案:B. 255

8位全1 = (11111111)₂ = 2⁸-1 = 255

考点:n 位二进制共有 2n 个不同编码,无符号时从 0 开始编号,最大值是 2n-1。

易错:直接记成 28=256,忘了编码从 0 起算,最大值应是 255。

🔴 C组 · 挑战(4题)

C1初赛真题

在C++中,表达式 25 / 3 * 3 + 25 % 3 的值是()

A. 24   B. 25   C. 27   D. 不确定

查看参考答案

答案:B. 25

25/3=8, 8*3=24, 25%3=1, 24+1=25。这就是恒等式 a/b*b+a%b==a。

考点:考查 / 与 % 的优先级相同、从左到右求值,以及恒等式 a / b * b + a % b == a 这一整数除法的基本性质。

易错:把 25 / 3 当作 8.33 带入浮点运算,或先算 25 % 3 改变求值顺序。

C2初赛真题

将十进制数 2024 转换为十六进制,结果是()

A. (7E8)₁₆   B. (7D8)₁₆   C. (8E8)₁₆   D. (7F8)₁₆

查看参考答案

答案:A. (7E8)₁₆

2024÷16=126余8,126÷16=7余14(E),7÷16=0余7 → 7E8

考点:十进制转十六进制同样用「除 16 取余、逆序排列」,只是余数 10~15 要写成 A~F。

易错:把余数 14 直接写成两位数字 14 而不是 E,或忘记把三组余数逆序。

C3初赛真题

已知某进制下 3×4=14,则该进制是()

A. 5   B. 6   C. 7   D. 8

查看参考答案

答案:D. 8

设进制为R,则 (14)ᵣ = 1×R + 4 = R+4。又3×4=12,所以R+4=12,R=8。

考点:设进制为 R,把结果 (14)R 按位权展开成 R + 4,与十进制下的乘积 12 建立等式解出 R。

易错:直接用 14 - 4 = 10 或 14 ÷ 4 得答案,忽略了结果本身也是 R 进制数。

C4初赛真题

下列关于补码的说法,错误的是()

A. 正数的补码等于其原码
B. 负数的补码是其绝对值取反加1
C. 补码的最高位为符号位,0表示负
D. 使用补码可以将减法转化为加法

查看参考答案

答案:C

补码最高位为符号位,0表示正,1表示负。C说反了。

考点:补码规则:正数的补码与原码相同,负数的补码是「反码加 1」;最高位始终是符号位,0 表示正、1 表示负。

易错:把符号位约定记反(以为 1 表示正),或误认为补码最高位只是普通数据位。