进位制,又称 进位系统(carry system)、进制系统、位置记法(positional notation)、位值记数法(place-value notation)、位置数值系统(positional numeral system),是一种能用有限种符号表示所有自然数的数字系统.一种进位制可以使用的符号数目称为 基数(radix)或 底数(base),基数为 的进位制称为 进制(),例如我们最常用的十进制,通常只使用 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 这十个符号来记数.进位指的是「当一个数字的某一位达到基数时,将其置为 0 并使高一位的数加一」的操作.
一般地,我们常将一个 进制的数记作 、、、 等.若基数隐含在上下文中我们也可以省略下标.注意此处的 并不是 个数的乘积,而是一串序列.
对于 进制数 ,其表示的值为 ;对一个数 ,设其 进制下的表示为 ,则有:
其中 .
数 在 进制下表示的长度为 .
我们一般通过添加小数点「」来表示小数1、添加负号「」来表示负数、在小数末尾的一段上添加上划线表示无限循环小数等.为了方便阅读,我们可以每隔若干数位添加间隔符号(如空格、、' 等),如 表示 .
在计算机中,比较常用的进位制有二进制、八进制和十六进制.
不同进位制间的转换
十进制转其他进制
这里以二进制为例来演示,其他进制的原理与其类似.
整数部分,把十进制数不断执行除 操作,直至商数为 ,之后从下到上,取所有余数的数字,即为二进制的整数部分数字.小数部分,则用其乘 ,取其整数部分的结果,再用计算后的小数部分依此重复计算,算到小数部分全为 为止,之后从上到下,取所有计算后整数部分的数字,即为二进制的小数部分数字.
例子
将 转化为二进制数.
整数部分:
小数部分:
即 .
实现
#include <algorithm>
#include <string>
// only non-negative
// 2 <= base && base <= 36
std::string from_dec(int x, int base) {
if (x == 0) return "0";
std::string res;
while (x) {
int r = x % base;
x /= base;
// 写法 1
res.push_back(r < 10 ? '0' + r : 'A' + r - 10);
// 写法 2
// const static std::string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// res.push_back(digits[r]);
}
std::reverse(res.begin(), res.end());
return res;
}
其他进制转十进制
还是以二进制为例.二进制数转换为十进制数,只需将每个位的值,乘以 次即可,其中 为当前位的位数,个位的位数为 .
例子
将 转换为十进制数.
即 .
实现
#include <cctype>
#include <string>
// only non-negative
// 2 <= base && base <= 36
int to_dec(std::string const& s, int base) {
int res = 0;
for (char c : s) {
res *= base;
if (isdigit(c))
res += c - '0';
else if (isupper(c))
res += c - 'A' + 10;
else if (islower(c))
res += c - 'a' + 10;
}
return res;
}
二进制/八进制/十六进制间的相互转换
一个八进制位可以用 3 个二进制位来表示(因为 ),一个十六进制位可以用 4 个二进制位来表示(),反之同理.
补数法
另请参阅:反码与补码
补数法(method of complements)是用正数表示负数,使得可以用和正数加法相同的算法/电路/机械结构来计算减法的方法.补数法广泛用于计算器和计算机的设计中,用以简化结构.
对 进制下 位数 ,其 数基补数(radix complement,称为 的补数)是 ,其 缩减数基补数(diminished radix complement,称为 的补数,简称 减补数)是 .在二进制下,数基补数叫做 的补数(two's complement),又叫做 补码;缩减数基补数叫做 的补数(ones' complement),又叫做 反码;在十进制下,数基补数又叫做 的补数(ten's complement),缩减数基补数又叫做 的补数(nine's complement);其他进制以此类推.
对 进制下的 位数 ,当计算 时,我们有如下方法(若结果超过 位,则舍弃高位):
- 考虑 的减补数 ,计算 ,则该结果的减补数即为答案.
- 考虑 的减补数 ,计算 ,直接加一即为答案.
- 考虑 的数基补数 ,计算 ,则该结果的数基补数即为答案.
- 考虑 的数基补数 ,计算 ,此即为答案.
另外,对 进制下的数,我们设 ,则 ,所以对 位数 ,设其数基补数在 进制下的表示为 ,则 即为 .这种「无限位的数」的思想可以推广为 进数(-adic number).
此外,我们有一个关于补数和无限循环小数的有趣定理:
例子
对于 ,我们有:
- ,
- ,
- ,
- .
证明
对定理中的 ,不难发现 且 .
对整数 ,令 ,我们有
0<f(i)=0.\overline{a_{i+1}a_{i+2}\cdots a_{nk}a_1a_2\cdots a_i}<1 \implies 0<pf(i) $$ 注意到 $pf(i)\in\mathbf{N}_+$ 且 $pf(i)\equiv ab^i\pmod p$,因此 $pf(i)=ab^i\bmod p$. 令 $S_n=\sum_{i=0}^{n-1}f(ik)=\sum_{i=0}^{n-1}0.\overline{a_{ik+1}a_{ik+2}\cdots a_{nk}a_1a_2\cdots a_{ik}}$,我们可以在各个小数间「交换」若干位(例如 $0.\overline{{\color{Orchid}{14}}{\color{RoyalBlue}{28}}{\color{YellowGreen}{57}}}+0.\overline{{\color{RoyalBlue}{28}}{\color{YellowGreen}{57}}{\color{Orchid}{14}}}+0.\overline{{\color{YellowGreen}{57}}{\color{Orchid}{14}}{\color{RoyalBlue}{28}}}=0.\overline{\color{Orchid}{141414}}+0.\overline{\color{RoyalBlue}{282828}}+0.\overline{\color{YellowGreen}{575757}}=0.\overline{\color{Orchid}{14}}+0.\overline{\color{RoyalBlue}{28}}+0.\overline{\color{YellowGreen}{57}}=14/99+28/99+57/99=1$),则\begin{aligned} S_n&=\sum_{i=0}^{n-1}0.\overline{a_{ik+1}a_{ik+2}\cdots a_{(i+1)k}}\ &=\sum_{i=0}^{n-1}a_{ik+1}a_{ik+2}\cdots a_{(i+1)k}/\left(b^k-1\right), \end{aligned}
pS_n=p\sum_{i=0}^{n-1}a_{ik+1}a_{ik+2}\cdots a_{(i+1)k}/\left(b^k-1\right)= \sum_{i=0}^{n-1} \left(ab^{ik}\bmod p\right),
\sum_{i=0}^{n-1}a_{ik+1}a_{ik+2}\cdots a_{(i+1)k}=\left(b^k-1\right)\frac{\sum_{i=0}^{n-1} \left(ab^{ik}\bmod p\right)}{p}.
若 $p\mid \left(b^k-1\right)$,注意到\left(b^k-1\right)a/p=a_1a_2\cdots a_k.\overline{a_{k+1}a_{k+2}\cdots a_{nk}a_1a_2\cdots a_k}-0.\overline{a_{1}a_{2}\cdots a_{nk}},
则有 $a_{k+1}a_{k+2}\cdots a_{nk}a_1a_2\cdots a_k=a_{1}a_{2}\cdots a_{nk}$,进而 $a_1a_2\cdots a_k=a_{k+1}a_{k+2}\cdots a_{2k}=\dots=a_{(n-1)k+1}a_{(n-1)k+2}\cdots a_{nk}$,即 $0.\overline{a_1a_2\cdots a_l}=0.\overline{a_1a_2\cdots a_k}$,这与 $l$ 的定义矛盾,因此 $p\nmid \left(b^k-1\right)$. 故存在正整数 $c=\dfrac{\sum_{i=0}^{n-1} \left(ab^{ik}\bmod p\right)}{p}$,使得\sum_{i=0}^{n-1}a_{ik+1}a_{ik+2}\cdots a_{(i+1)k}=c\left(b^k-1\right).
推论
对上述的 ,有
广义进制系统
标准的进制系统中,基数 总是一个固定的正数,每个数位在 种不同的符号中选取,用以表示一个非负数(不考虑小数点和负号).实际上仍有许多记数系统和进制系统有类似的特征,但不完全符合进制系统的规定,我们把这样的记数系统称为 广义进制系统 或 非标准进制系统(Non-standard positional numeral systems).下面介绍几种常见的广义进制系统.
双射记数系统
标准的进制系统并不能和其表示的数字建立双射,如 、、 表示的数是相同的3,而 双射记数系统(bijective numeral system)可以和其表示的数字建立双射.
双射 进制()使用数集 来唯一表示一个数,规则如下:
- 用空串表示 ;
- 用非空串 表示数 .
对一个正数 ,设其双射 进制下的表示为 ,则有:
其中 .
例如 Microsoft Excel 的列标签采用的就是双射 进制.
在双射记数系统下,我们有 一进制,一进制下的非空串只由 构成,串的长度即为其表示的数.
类似 补数法 下的叙述,在 的双射 进制下,我们设 ,则 ,进而 ,所以设 在双射 进制下的表示为 ,则 即为 .
下面是双射 进制数的一些性质:
- 长度为 的数共有 种.
- 时,数 在双射 进制下表示的长度为 .
- 时,若一个数 在 进制下的表示中不含 ,则其在 进制下的表示和在双射 进制下的表示相同.
双射 进制转十进制和 进制转十进制的代码相同,下面是十进制转双射 进制的参考实现
实现
#include <algorithm>
#include <string>
// only non-negative
// 1 <= base && base < 36
std::string from_dec_bi(int x, int base) {
std::string res;
while (x > 0) {
int q = (x + base - 1) / base - 1, r = x - q * base;
x = q;
// 写法 1
res.push_back(r < 10 ? '0' + r : 'A' + r - 10);
// 写法 2
// const static std::string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// res.push_back(digits[r]);
}
std::reverse(res.begin(), res.end());
return res;
}
有符号位数进制
有的进位制系统允许数位中出现负数,例如 平衡三进制.
Gray 码
主条目:格雷码
Gray 码又叫 循环二进制码 或 反射二进制码(reflected binary code,RBC),是一种特殊的二进制数字系统,常用于数据校验中.
非正基数进制
我们知道对于 进制数 ,其表示的值为 ,我们稍加修改即可定义 进制数 表示 ,其中 .例如 .这种进制系统叫做 负底数进制(negative-base system).
类似地,我们也可以定义 复底数进制(complex-base system),如 进制(quater-imaginary base、quater-imaginary numeral system),我们还可以定义 非整数进位制(non-integer base of numeration)用于表示实数的 展开(-expansion)等.
混合基数进制
标准的进制系统中,每一个数位对应的基数都是固定的,而混合基数进制允许每一个数位对应不同的基数.混合基数进制系统最常见的应用就是计时:小时采用 进制,分钟和秒采用 进制.
在 进制下表示的数为 ,而在混合基数进制下,其表示的数为 ,其中 为 对应的基数.
在算法竞赛中,最常见的混合基数进制系统是 阶乘进制(factorial number system),其中的数可以记作 ,其表示的数为 4.阶乘进制在算法竞赛中的应用可参见 Lehmer 码/Cantor 展开.
实现(十进制转阶乘进制)
#include <algorithm>
#include <string>
// only non-negative
std::string from_dec_factorial(int x) {
if (x == 0) return "0";
std::string res;
int base = 1;
while (x) {
int r = x % base;
x /= base++;
// 写法 1
res.push_back(r < 10 ? '0' + r : 'A' + r - 10);
// 写法 2
// const static std::string digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// res.push_back(digits[r]);
}
std::reverse(res.begin(), res.end());
return res;
}
实现(阶乘进制转十进制)
#include <cctype>
#include <string>
// only non-negative
int to_dec_factorial(std::string const& s) {
int res = 0, base = s.size();
for (char c : s) {
res *= base--;
if (isdigit(c))
res += c - '0';
else if (isupper(c))
res += c - 'A' + 10;
else if (islower(c))
res += c - 'a' + 10;
}
return res;
}
C++ 中的实现
对于非负数,C++ 中用 <前缀><数位><后缀> 表示一个整数字面量,其中 <数位> 和 <后缀> 均可以为空.<后缀> 用于表示字面量的类型,如 u 或 U 表示该字面量为 unsigned、l 或 L 表示该字面量为 long 等.对于 <前缀>:
- 当
<前缀>为0x或0X时表示十六进制字面量,此时<数位>中的字符只能在0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, A, b, B, c, C, d, D, e, E, f, F中选取.例如0x1234ABCD为 ; - 当
<前缀>为0时表示八进制字面量,此时<数位>中的字符只能在0, 1, 2, 3, 4, 5, 6, 7中选取.例如01234567为 ;5 - 当
<前缀>为1、2、3、4、5、6、7、8或9时表示十进制字面量,此时<数位>中的字符只能在0, 1, 2, 3, 4, 5, 6, 7, 8, 9中选取; - C++14 起,当
<前缀>为0b或0B时表示二进制字面量,此时<数位>中的字符只能在0, 1中选取.例如0b11001010为 .
参考资料与注释
- Positional notation - Wikipedia
- Method of complements - Wikipedia
- Non-standard positional numeral systems - Wikipedia
- Bijective numeration - Wikipedia
- Midy's theorem - Wikipedia
- N3472 - Binary Literals in the C++ Core Language