跳转至

进制转换与位运算


整数之间的进制转换

十进制转其他进制(除基取余法

步骤:对 $N$ 不断做

\[ N \div b = q,\quad r = N \bmod b, \]

记录余数 $r$,然后令 $N \leftarrow q$;当 $N=0$ 时,把余数逆序拼回即为目标进制表示。

示例(完整步骤):把 $123_{10}$ 转为二进制

\[ \begin{aligned} 123 \div 2 &= 61\ \ \ \mathrm{r}=1\\ 61 \div 2 &= 30\ \ \ \mathrm{r}=1\\ 30 \div 2 &= 15\ \ \ \mathrm{r}=0\\ 15 \div 2 &= 7\ \ \ \mathrm{r}=1\\ 7 \div 2 &= 3\ \ \ \mathrm{r}=1\\ 3 \div 2 &= 1\ \ \ \mathrm{r}=1\\ 1 \div 2 &= 0\ \ \ \mathrm{r}=1 \end{aligned} \quad\Rightarrow\quad 123_{10}=1111011_2. \]

代码实现

// 十进制 n 转 b 进制的自定义函数
// 时间复杂度:O(logn) 
string f(int n, int b) 
{
    string s;
    while (n != 0)
    {
        int r = n % b;
        if (r < 10)
            s += char(r + 48); 
        else
            s += char(r + 55); 
        n = n / b; 
    }
    reverse(s.begin(), s.end());
    return s; 
}

其他进制转十进制(按位相加)

若一个数在进制 $b$ 下的位串为 $d_k d_{k-1}\dots d_1 d_0$,则它在十进制的值为

\[ N=\sum_{i=0}^{k} d_i\, b^i. \]

把每位乘以其位权再相加。


示例:$\text{0x3F}$(十六进制)转换为十进制:

\[ \text{0x3F} = 3\times16^1 + 15\times 16^0 = 48 + 15 = 63. \]

代码实现如下

// b 进制的 s 转 10 进制的自定义函数
string f(string s, int b)
{
    long long sum = 0;
    for (int i = 0; i < s.size(); i++)
    {
        int d = 0;
        if (s[i] <= '9') 
            d = s[i] - 48;
        else
            d = s[i] - 55;
        sum = sum * b + d; 
    }
    return sum;
}

例题题目:把 $2025_{10}$ 转成二进制(写出完整结果)。📝

解析(逐步拆解):

把 $2025$ 用 2 的幂拆分(写出从大到小的位权):

\[ \begin{aligned} 2^{10} &= 1024,\\ 2^{9} &= 512,\\ 2^{8} &= 256,\\ 2^{7} &= 128,\\ 2^{6} &= 64,\\ 2^{5} &= 32,\\ 2^{4} &= 16,\\ 2^{3} &= 8,\\ 2^{2} &= 4,\\ 2^{1} &= 2,\\ 2^{0} &= 1. \end{aligned} \]

选取能相加到 $2025$ 的幂:

\[ 2025 = 1024+512+256+128+64+32+8+1. \]

对应位(从 $2^{10}$ 到 $2^0$)为:$1,1,1,1,1,1,0,1,0,0,1$,所以

\[ 2025_{10}=11111101001_2. \]

答案:$11111101001_2$ ✅


进制字面量标记法

在 C/C++ 里,不同进制的数有不同的写法:

  • 二进制 bin:前缀 0b。例如:0b1001 = 9₁₀
  • 八进制 oct:前缀 0。例如:0123 = 83₁₀(⚠️ 不是 $123$ 十进制!)
  • 十进制 dec:直接写数字。例如:123 = 123₁₀
  • 十六进制 hex:前缀 0x。例如:0x3f = 63₁₀

📌 英文缩写对照

  • bin(二进制)
  • oct(八进制)
  • hex(十六进制)

示例代码

#include <bits/stdc++.h>
using namespace std;
int main() 
{
    cout << 0b1001 << "\n"; // 二进制,9
    cout << 123     << "\n"; // 十进制,123
    cout << 0123    << "\n"; // 八进制,83
    cout << 0x3f    << "\n"; // 十六进制,63
}

二/八/十六之间的便捷转换

目标:在考试中快速实现二进制与十六进制,二进制与八进制互转(不包括八进制与十六进制互转),不必每次先转到十进制。

核心规则

  • 每一位十六进制(hex)对应 $4$ 位二进制

$$ 1\ \text{hex digit} \iff 4\ \text{binary bits}. $$

  • 每一位八进制(oct)对应 $3$ 位二进制

$$ 1\ \text{octal digit} \iff 3\ \text{binary bits}. $$

做法:把二进制从低位向高位每 $4$ 或 $3$ 位分组,不足位在高位用 $0$ 补齐,然后逐组替换为 十六进制或八进制 的数字。


练习 1

把 $\text{0x7B}$ 转成二进制:

\[ 7 = 0111,\quad B = 1011 \quad\Rightarrow\quad 01111011_2. \]

通常去掉高位的前导 $0$,写作 $1111011_2$。


练习 2

把 $\text{0xA3F}$ 转为二进制和八进制。📝

解析

  • 先 十六进制转二进制:

$A=1010,\quad 3=0011,\quad F=1111$

所以

$$ \text{0xA3F} = 1010\ 0011\ 1111_2 = 101000111111_2. $$

  • 二进制转八进制:从右向左每 $3$ 位分组(不足高位补 $0$):

$$ 101\ 000\ 111\ 111 \Rightarrow 5\ 0\ 7\ 7 \Rightarrow 5077_8. $$

答案:$101000111111_2,\quad 5077_8$ ✅


小数部分的进制转换

二进制小数转十进制

例如二进制小数 $1010.01_2$ 转为十进制:

其转换规则同整数部分,区别仅仅在于需要累加小数部分的权重。即:

\[ 1010.01_2=1\times 2^3+0\times 2^2+1\times 2^1+0\times 2^0+0\times 2^{-1}+1\times 2^{-2} = 10.25 \]

$a^{-b}=\frac{1}{a^b}$

其余进制小数转十进制以此类推。


十进制小数转二进制

例如十进制小数 $0.25$ 转为二进制:

方法:乘 $2$ 取整法,直到小数部分为 $0$ 停下。

\[\begin{aligned} 0.25\times 2 &= 0.5\ \texttt{整数部分为}\ 0\\ 0.5\times 2 &= 1.0\ \texttt{整数部分为}\ 1\\ \end{aligned}\]

小数部分为 $0$ 结束。

整数部分 正序排列。因此十进制小数 $0.25$ 的二进制为 $0.01_2$。

但有些十进制小数没有精确的二进制表示请看下面示例。


示例(有限情况)

把 $0.625_{10}$ 转为二进制:

\[ \begin{aligned} 0.625\times2 &= 1.25 \quad (a_1=1),\quad f=0.25\\ 0.25\times2 &= 0.5 \quad (a_2=0),\quad f=0.5\\ 0.5\times2 &= 1.0 \quad (a_3=1),\quad f=0 \end{aligned} \]

因此 $0.625_{10}=0.101_2$(有限)。


示例(循环情况)

把 $0.1_{10}$ 转为二进制(前若干位):

\[ \begin{aligned} 0.1\times2 &= 0.2\quad (0)\\ 0.2\times2 &= 0.4\quad (0)\\ 0.4\times2 &= 0.8\quad (0)\\ 0.8\times2 &= 1.6\quad (1)\ \text{余 }0.6\\ 0.6\times2 &= 1.2\quad (1)\ \text{余 }0.2\\ 0.2\times2 &= 0.4\quad (0)\ \text{(重复)} \end{aligned} \]

得到循环节,前 $12$ 位为

\[ 0.000110011001\ldots_2 \]

因此 $0.1_{10}$ 在二进制下是 无限循环 的。

十进制小数转其余进制以此类推。


原码、反码、补码

在进行位运算前,我们需要理解计算机中数值的二进制表示方式:

1. 原码(Sign-Magnitude)

  • 正数的原码就是其二进制形式。加一个符号位(最高位为 $0$)
  • 负数的原码是其二进制,加一个符号位(最高位为 $1$)。

例如:

  • +1 的原码:$\textcolor{red}00000000\ 0000000\ 00000000 \ 00000001$
  • -1 的原码:$\textcolor{red}10000000\ 0000000\ 00000000 \ 00000001$

2. 反码(Ones' Complement)

  • 正数的反码与原码相同。
  • 负数的反码是对其原码除符号位外的每一位取反。

例如:

  • +1 的反码:$\textcolor{red}00000000\ 00000000\ 00000000 \ 00000001$
  • -1 的反码:$\textcolor{red}11111111\ 11111111\ 11111111 \ 11111110$

3. 补码(Two's Complement)

  • 正数的补码仍与原码相同。
  • 负数的补码 = 反码 $+ 1$。

例如:

  • +1 的补码:$\textcolor{red}00000000\ 00000000\ 00000000 \ 00000001$
  • -1 的补码:$\textcolor{red}11111111\ 11111111\ 11111111 \ 11111111$

计算机中 所有数值的存储与计算 都使用 补码


位运算

按位与:&

定义:对应位都为 $1$ 时结果位为 $1$,否则为 $0$。常用于掩码(取位)。

性质

  • $a\&b = b\&a$ (交换律)
  • $a\&a=a$

示例:$5\&3$

\[ 5=00000101_2,\quad 3=00000011_2\quad\Rightarrow\quad 00000001_2=1. \]

示例:$-5\&-3$

注意负数要算出补码后计算。

\[ -5=11111011_2,\quad -3=11111101_2\quad\Rightarrow\quad 11111001_2. \]

最后的计算结果也为补码,如果需要求出十进制下的结果,需要将 $11111001_2$ 转回原码可以得到是 $10000111_2=-7_{10}$。

经典应用

  • n >> i & 1 可以判断 $n$ 的二进制第 $i$ 位是否为 $1$。
    • 注意二进制下称最低为通常为第 $0$ 位。

按位或:|

定义:对应位任一为 $1$ 时结果位为 $1$。常用于置位

性质

  • $a|b=b|a$
  • $a|a=a$

示例:$5|2$

\[ 5=00000101_2,\quad 2=00000010_2\ \Rightarrow\ 00000111_2=7. \]

示例:$-5|-3$

注意负数要算出补码后计算。

\[ -5=11111011_2,\quad -3=11111101_2\quad\Rightarrow\quad 11111111_2. \]

最后的计算结果也为补码,如果需要求出十进制下的结果,需要将 $11111111_2$ 转回原码可以得到是 $10000001_2=-1_{10}$。


异或:^(XOR)

定义:对应位相异时为 $1$,相同时为 $0$。常用于切换/无进位相加

性质

  • $x\oplus x = 0$
  • $x\oplus 0 = x$
  • 具有交换和结合律。
  • 异或的数学符号是 $\oplus$

示例:$5\oplus3$

\[ 5=0101_2,\quad 3=0011_2\ \Rightarrow\ 0110_2=6. \]

示例:$-5\oplus -3$

注意负数要算出补码后计算。

\[ -5=11111011_2,\quad -3=11111101_2\quad\Rightarrow\quad 00000110_2. \]

最后的计算结果也为补码,如果需要求出十进制下的结果,需要将 $00000110_2$ 转回原码(正数原反补均相同)可以得到是 $00000110_2=6_{10}$。


取反:~

定义:按位取反(0↔1)。在计算机中通常基于二补码,其数值关系为:

\[ \texttt{~x} = -x - 1\quad(\text{在有符号整型语义下}). \]

注意:取反结果受位宽影响(例如 $8$ 位、$32$ 位等)。

示例($8$-bit,无符号解释):

\[ x=5\ (00000101_2)\ \Rightarrow\ \sim x=11111010_2 = 250\ (\text{unsigned}). \]

练习

  • :在 8-bit 下,$x=150$,求 $\sim x$ 的无符号结果。
  • :$150=10010110_2$,取反得 $01101001_2=105$。
  • :$105$(无符号)。

左移:<<

意义:把位向左移动,低位补 $0$。在不溢出的情况下,等价于乘以 $2^k$:

\[ x<<k = x\times 2^k. \]

注意:对负数或移位超出位宽,在 C/C++ 中可能 未定义

练习

  • :$1<<3$ 等于多少?
  • :$8$(因 $1\times2^3=8$)。

通常使用 1 << n 快速获取 $2^n$。但要注意溢出问题。


右移:>>

意义:把位向右移动。对无符号数是逻辑右移(高位补 $0$),对有符号数很多实现做算术右移(高位补符号位),近似等价于整除 $2^k$:

\[ x>>k = \left\lfloor \frac{x}{2^k}\right\rfloor \quad (\text{对非负}~x). \]

注意:带符号右移是否补符号位取决实现,不要在考试代码题中依赖未定义行为。

通常使用 a + b >> 1 代替 (a + b) / 2


总结

所有的位运算计算都是用 补码 计算。

运算符 名称 描述 示例(按位)
& 同为 $1$ 结果为 $1$ 1101 & 1011=1001
| 有 $1$ 则为 $1$ 1101 | 1011 = 1111
^ 异或 相同为 $0$ 不同为 $1$ 1101 ^ 1011 = 0110
~ 取反 所有位翻转 ~1101 = 0010(按位)
<< 左移 所有位左移 $n$ 位,低位补 $0$ 0001 << 2 = 0100
>> 右移 所有位右移 $n$ 位,高位补符号位(有符号)或 $0$(无符号) 1000 >> 2 = 0010(无符号)

位运算常见技巧与应用

取第 $i$ 位 —— (n >> i) & 1

含义:把 $n$ 二进制第 $i$ 位右移到最低位,再与 $1$ 按位与,得到 $0$ 或 $1$。

示例:令 $n=13$,求第 $2$ 位(最低位称之为第 $0$ 位):

  • $13=1101_2,\quad (13>>2)=11_2=3,\quad 3\&1=1$,所以第 $2$ 位为 $1$。

取最低位的 1(lowbit) —— x & -x

含义:表达式 x & -x 返回 $x$ 中最低位的 $1$ 对应的 (例如 $4$、$8$ 等)。

原理(直观):在二补码下 $-x = \overline{x}+1$,与 $x$ 按位与只保留最低的 1。

注意负数要算出补码后在计算。熟知本技巧含以后就可以直接利用 $x$ 的二进制求得结果。

示例:$x=12=1100_2$,则

\[ x\&(-x) = 1100_2 \& 0100_2 = 0100_2 = 4. \]

清除最低位的 1 —— x & (x - 1)

含义:表达式 x & (x - 1) 将把 $x$ 的最低位的 $1$ 变为 $0$,其余位不变。常用于计数二进制下 $1$ 的个数。

例子:$x=12=1100_2$,

\[ x\&(x-1) = 1100_2 \& 1011_2 = 1000_2 = 8. \]

应用:计算 $x$ 二进制下 $1$ 的个数的一种写法,时间复杂度也是 $O(\log{x})$。

int count(int x) // 求二进制下有几个 1
{
    int cnt = 0;
    while (x) 
    {
        x &= x - 1;
        ++cnt;
    }
    return cnt;
}

判断 $x$ 是否为 $2$ 的幂

公式

\[ \text{isPow2}(x)\iff x>0\ \text{且}\ (x\& (x-1))==0. \]

(因为 $2$ 的幂在二进制中只有一个 $1$。)

练习

  • :$x=16$,判断是否为 2 的幂?
  • :$16\&(16-1)=16\&15=0$,且 $x>0$,所以是。
  • :是($\text{True}$)。✅

统计 1 的个数(popcount)

方法:使用内置函数(如 GNU C++ 的 __builtin_popcount(x))或循环 x &= x - 1 或直接数位拆分。

int count(int x)
{
    int cnt = 0;
    while (x)
    {
        if (x % 2 == 1) cnt++;
        x /= 2;
    }
    return cnt;
}

其余知识点总结

基本存储单位

  • 位:bit。
  • 字节:byte。1 byte = 8 bit
    • byte 也可以用 B 表示
  • 千字节:KB。1 KB = 1024 B(不是 $1000$)
  • 1 MB = 1024 KB = 1024 × 1024 B = 1,048,576 B
  • 1 GB = 1024 MB

记忆技巧:计算机中的“千”通常是 $2$ 的幂($1024 = 2^{10}$)。

字节是计算机内存的基本单位,而位是计算机运算的最小单位。


常见 C/C++ 基本类型内存的占用

  • char:$1$ 字节
  • short:$2$ 字节
  • int:$4$ 字节
  • long long:$8$ 字节
  • float:$4$ 字节
  • double:$8$ 字节
  • bool:$1$ 字节

数组内存的计算方法

计算数组/变量占用内存的基本思路:元素数量 × 每元素字节数 = 总字节数,再除以 $1024$ 或 $1024^2$ 转成 KB / MB。

例子:

  • int a[100]100 × 4 = 400 B
    • = 400 / 1024 ≈ 0.390625 KB
    • = 400 / 1024 / 1024 ≈ 0.00038147 MB (非常小)
  • int a[1000_000]1,000,000 × 4 = 4,000,000 B
    • $\approx$ 3.814697265625 MB
  • long long b[1000000]1,000,000 × 8 = 8,000,000 B
    • $\approx$ 7.62939453125 MB

常见导致 MLE 的坑

  • 不要在栈上开超大数组(如 int a[10000000]; 可能栈溢出)。栈通常只有几 MB(视系统/判题机而定)。
    • 推荐:把大数组声明为全局变量。
  • 全局变量开了超大的数组,例如 int a[1000000000] 大约需要 $3814$ MB 的内存。或者开了 int a[100000][100000] 超大的二维数组爆内存。
  • 使用合适的数据类型:
    • 不需要 long long 就别用,int(4B)比 long long(8B)省内存。

不同类型的取值范围

示例: int 的大小

  • int4 字节 = 32 位

C/C++ 中的 int 默认是 有符号的,使用 补码表示

  • 最高位(第 $31$ 位)是符号位:

  • 0 表示正数

  • 1 表示负数
  • 正数:二进制与无符号相同。
  • 负数:其补码 = 对应正数按位取反 $+ 1$。

正数范围

  • 最高位为 0,剩下 $31$ 位用于表示数值。
  • 最大值:0111...111($31$ 个 $1$)
\[ 2^{30}+2^{29}+\ldots+2^1+2^0 = 2^{31}-1 = 2147483647 \]

负数范围

  • 最小值:1000...000(符号位 $1$,其他全 $0$)
\[ -2^{31} = -2147483648 \]

int 总结范围

  • 最小值:-2,147,483,648
  • 最大值:2,147,483,647

所以:

\[ \text{int} \in [-2^{31}, \; 2^{31}-1] \]

无符号 unsigned int

  • 也是 $4$ 字节 = $32$ 位。
  • 范围:
\[ 0 \; \text{到} \; 2^{32}-1 = 4,294,967,295 \]

小扩展

  • short($2$ 字节 = $16$ 位):
\[ -2^{15} \sim 2^{15}-1 = [-32768, 32767] \]
  • long long($8$ 字节 = $64$ 位):
\[ -2^{63} \sim 2^{63}-1 \approx [-9\times 10^{18},9\times 10^{18}] \]

记忆方法

  • $n$ 位有符号整数范围是:
\[ [-2^{n-1}, \; 2^{n-1}-1] \]
  • $n$ 位无符号整数范围是:
\[ [0, \; 2^n-1] \]

常见整数类型与范围对照表

数据类型 字节数 位数 有符号范围 无符号范围
char $1$ B $8$ $-128 \sim 127$ $0 \sim 255 $
short $2$ B $16$ $-32,768\sim 32,767 $ $0\sim 65,535$
int $4$ B $32$ $-2,147,483,648\sim 2,147,483,647 $ $0\sim 4,294,967,295 $
long long $8$ B $64$ $-9,223,372,036,854,775,808\sim 9,223,372,036,854,775,807$ $0\sim 18,446,744,073,709,551,615$