进制换算:2 的幂进制对齐

二进制、四进制、八进制、十六进制、三十二进制之间不需要「除基取余」 —— 每 m 位二进制正好是一位 2m 进制。十进制才需要真的除。这一页把这个「对齐」讲透,外加另外两个方向(十进制 ↔ 任意进制)的模板。

三句话速览

1. 一张总表:谁和谁能对齐

进制等于 2 的几次方每一位对应几位二进制例子
二进制(2)211 位1011
四进制(4)222 位1011 → 10|11 = 23
八进制(8)233 位101 011 → 538
十六进制(16)244 位1010 1011 → AB
三十二进制(32)255 位0001 0101 → 15
十进制(10)不是 2 的幂—只能除基取余

2. 为什么能对齐

关键在于「容量刚好对上」:

一位十六进制能表示 0 ~ 15,共 16 个值;而 4 位二进制也正好表示 0 ~ 15,共 16 个值。
两边的取值范围完全一样,所以可以一位对四位地直接替换 —— 不需要进位、不需要借位、不会跨组。

而 10 进制没有这个性质:一位十进制是 0~9(10 个值),二进制 3 位是 0~7、4 位是 0~15,都对不上。所以二进制和十进制之间必须真的做除法。

3. 二进制 → 十六进制

从右往左、每 4 位切一组,每组算出一个 0~15 的值,再换成对应的十六进制字符(0-9、A-F)。每组内部的权重是 8 / 4 / 2 / 1。

string binToHex(const string& s) {              // 从右往左每 4 位一组
    static const char* hex = "0123456789ABCDEF";
    string t = s;
    while (t.size() % 4) t = "0" + t;            // 左端补零,凑成 4 的倍数
    string out;
    for (size_t i = 0; i < t.size(); i += 4) {
        int v = 0;
        for (int k = 0; k < 4; k++) v = v * 2 + (t[i + k] - '0');   // 逐位累加
        out += hex[v];
    }
    size_t p = out.find_first_not_of('0');
    return p == string::npos ? "0" : out.substr(p);
}

实测输出:

二进制十六进制说明
1010A10 → A
10101010AA两组都是 A
110101A补零成 0001 1010
11111111FF两组都是 15

如果题目只问「有多少个 A」(比如 PTA 那道 7-6),连十六进制字符都不用拼,直接数「有多少组 4 位等于 1010」:

for (int i = 0; i < len; i += 4) {
    int v = (s[i]-'0')*8 + (s[i+1]-'0')*4 + (s[i+2]-'0')*2 + (s[i+3]-'0')*1;
    if (v == 10) cnt++;                          // 10 就是 A
}

4. 分组方向(最容易错的地方)

必须从右往左分组,因为进制对齐是「低位对齐」的。长度不是 4 的倍数时,要在左端补零。

实测:从左按 4 位切会算错。
11010(5 位)正确结果是 1A(补成 0001 1010);如果从左切、丢掉不满 4 位的尾巴,只会得到 D(把 1101 当成一组)。

另外记一个「白送」的性质:如果长度本来就是 4 的倍数(比如题目给的 4n 位二进制),那么从左往右切和从右往左切结果一样 —— 这时候前导零只是变成不产生影响的 0 组。反过来,长度不是 4 的倍数时一定不能从左切。

5. 十六进制 → 二进制

反过来更简单:每一位十六进制展开成固定的 4 位二进制,不足 4 位就在左边补零。

string hexToBin(const string& h) {              // 每一位展开成 4 位
    static const char* hex = "0123456789ABCDEF";
    string out;
    for (char c : h) {
        int v = isdigit((unsigned char)c) ? c - '0' : toupper(c) - 'A' + 10;
        for (int k = 3; k >= 0; k--) out += char('0' + ((v >> k) & 1));
    }
    size_t p = out.find_first_not_of('0');
    return p == string::npos ? "0" : out.substr(p);
}
十六进制二进制
A510100101
1A11010
F1111

6. 十进制这一侧:Horner 与除基取余

只要有一边是十进制(或任何非 2 的幂进制),对齐就不成立,只能用这两种模板。两个都是 O(位数)。

任意进制 → 十进制:Horner 累加

long long toDec(const string& s, int base) {    // v = v * base + d
    long long v = 0;
    for (char c : s) {
        int d = isdigit((unsigned char)c) ? c - '0' : toupper(c) - 'A' + 10;
        v = v * base + d;
    }
    return v;
}

实测:"1010"(2) = 10,"777"(8) = 511,"FF"(16) = 255。

十进制 → 任意进制:除基取余再倒序

string decToBase(long long x, int base) {
    if (x == 0) return "0";
    static const char* dig = "0123456789ABCDEF";
    string out;
    while (x) { out += dig[x % base]; x /= base; }   // 先得到低位
    reverse(out.begin(), out.end());                 // 倒过来才是正常顺序
    return out;
}

实测:255 → FF(16 进制)、511 → 777(8 进制)、10 → 1010(2 进制)。

7. 四个坑

  1. 字符转数字:数字字符用 c - '0';字母用 c - 'A' + 10(先 toupper 统一成大写,否则小写 a 会得到错误的值)。
  2. 分组方向:从右往左;长度不是 m 的倍数时在左端补零(见第 4 节实测)。
  3. 前导零:输出「数值」时要剥掉(否则会多出前导 0);但如果题目问的是「按固定位宽表示时某字符出现几次」,前导零不能随便丢 —— 要看清问的是数值还是固定位宽表示。
  4. 溢出:32 位 int 装不下 16 位十六进制(要 long long);再大就只能留在字符串里做加减乘。

8. 小测

Q1. 十六进制一位对应几位二进制?
Q2. 二进制串 11010 的十六进制是多少?
Q3. 「任意进制串 → 十进制」用哪套模板?
卡住时的自查顺序
① 我要转的两个进制,有一个是 10 的倍数吗?有 → 用 Horner / 除基取余;没有 → 用对齐(每 m 位一组)。
② 对齐时分组是从右往左的吗?长度是 m 的倍数吗?
③ 字符转数字写对了吗(数字 - '0'、字母 - 'A' + 10、大写先统一)?
④ 长度够不够 long long?

关联:0415 前缀和 / 差分 / 单调栈(逐位处理) · 关联:0004 循环与数组(字符串遍历)

书籍 《C++ Primer Plus》第 3 章(进制与整型字面量)· 《深入理解计算机系统》第 2 章(位与位运算)

在线 OI-Wiki · 进制 · cppreference · bitset

本页所有输出都在本机 g++ 16.2.0 下实际运行得到 · 返回资源列表