这周计算机原理课收到楼sir的一个作业:要自己实现一套整数编码的数码转换与若干运算,并分析。
我拿到的是补码,其他的队友分别要实现一套原码、移码或者自行设计一套“帅码”(666)。
编码之前…
编程语言: C
听说规定要用C语言造这个轮子,真是遗憾,要是C++还可以各种运算符重载可以很优雅。
约束
那好吧,在编码之前,先做个约束。
不能使用任何形式的
int
类型,包括中间变量,因为int
本身就是一个补码实现。
看起来,这次的任务就是基于unsigned int
实现一个int
类型(32位)。使用
unsigned int
作为 二进制串word
的容器结构。
typedef unsigned int word;
不能使用
%d
输出word
, 而要使用 码转数函数mtoa
将word
转化为char*
。
当然也不能用%d
读入word
,而要使用 数转码函数atom
将char*
转化为word
。
本质上使用 %d 就是一个 补码数码转换 的实现,因此我们要避免使用它。
函数原型
函数签名 | 注释 |
---|---|
word atom(char*) | 从字符串中以带符号十进制的格式读入,转换为补码 |
char* mtoa(word) | 将补码转换为带符号十进制的字符串格式 |
word madd(word, word) | 补码加法 |
word msub(word, word) | 补码减法 |
word mmul(word, word) | 补码乘法 |
word mdiv(word, word) | 补码整除 |
以上函数原型均为楼sir一手定义,于是我就照着来咯。
数码转换
atom函数与mtoa函数在数学上互逆的,即
然而在计算机中受到机器精度限制,并不能好好地做运算。
考虑到这点,我们可以给函数加上定义域与值域的限制。
特别地,对于32位补码,定义:
atom:[−231,231)→[0,232)
mtoa:[0,232)→[−231,231)
构成双射函数,于是求这个函数的反函数得到:
atom,mtoa的定义是等价的,都可以称为补码的定义。
接下来看一下在0-1串内部的一些位运算:
二进制码按位取反函数的代数代换
设有0-1串
则有
显然有
于是
按位取反运算:Not(m)在 [0,2n) 中封闭。
补码:取相反数的算法证明
设
m≠0
是一个
n
位的0-1串,证明:
证:
代入按位取反公式:
简单化简:
若 m∈(0,2n−1)→2n−m∈(2n−1,2n)
根据补码的定义
−mtoa(m)=−mmtoa(2n−m)=−2n+(2n−m)=−m
所以:
mtoa(2n−m)=−mtoa(m)若 m∈[2n−1,2n)→2n−m∈(0,2n−1]
根据补码的定义
−mtoa(m)=−(−2n+m)=2n−mmtoa(2n−m)=2n−m
所以:
mtoa(2n−m)=−mtoa(m)
综上所述,
证毕。
所以,非0数的补码按位取反再加一即为其相反数的补码。
特别地,0的相反数的补码是其本身,这个可以单独验证。
补码加法
对于n位0-1串
m1,m2
,证明:
引理2: mtoa(x)≡x(mod2n),x∈[0,2n)
证明:
1. 若 x∈[0,2n−1)
mtoa(x)=x≡x(mod2n)
2. 若 x∈[2n−1,2n)
mtoa(x)=−2n+x≡x(mod2n)
证毕。
证:
由引理2:
由同余定理得
所以,
证毕。
这个部分证明了:两数的和的补码与两数的补码的和(高位丢弃)对 2n 同余。
补码减法
对于n位0-1串
m1,m2
,证明
mtoa(m1)−mtoa(m2)≡mtoa((m1−m2)mod2n)(mod2n)
。
证:
由引理2:
由同余定理:
所以,
这个部分证明了:两数的差的补码与两数的补码的差对 2n 同余。
补码乘法
终于到乘法了,这个部分的数学部分就比较麻烦了,二进制串(向量)乘法本质是一个离散卷积。关于其算法,普通可以使用 O(n2) 的朴素做法,也可以用FFT1算法进行优化到 O(nlog(n)) 。
// TODO
鉴于楼sir尚未要求实现乘法/除法,我暂时先不做这部分的工作。
编码实现
数码转换
数转码:
word atom(char* str){
word res;
sscanf(str, "%d", &res);
return res;
}
码转数:
char* mtoa(word w){
char* res = (char*) malloc(sizeof(char) * 12);
sprintf(res, "%d", w);
return res;
}
以上是错误示范。尽管完美地完成了补码的数码转换,但是这样就违反了一开始的约束了,用了 C 标准库中的轮子。
mtoa中给res开的空间是比较宽松的。32位整数的十进制表示不会超过11位字符,加上字符串结束符\0
最多12位。
数转码
正如之前的错误示范的正确思路,扫描字符串即可。
word atom(char* str){
word res = 0, flag = 0;
for(word i = 0; str[i]; i++){
if('0' <= str[i] && str[i] <= '9')
res = 10 * res + str[i] - '0';
else if(i == 0 && str[i] == '-')
flag = 1; // negative flag
else break; // illegal character
}
// if res != 0 and negative flag
// consider about the input "-0"
if(res && flag)
res = ~res + 1;
return res;
}
码转数
这个需要将码逐位取出(或者一次取多位)然后转化为字符串即可。
char* mtoa(word w){
char* res = (char*) malloc(sizeof(char) * 12);
word flag = w >> 31; // get the topest digit
word cur = 0;
if(flag) { // if negative
res[cur++] = '-';
w = ~w + 1;
}
word startCur = cur;
while(w){
res[cur++] = w % 10 + '0';
w /= 10;
}
word endCur = cur - 1;
// let's reverse
while(startCur < endCur){
char temp = res[startCur];
res[startCur++] = res[endCur];
res[endCur--] = temp;
}
if(cur == 0)
res[cur++] = '0'; // if 0
res[cur] = '\0';
return res;
}
这个操作相对低效一些并没有太大的关系,这个通常跟I/O相关,I/O瓶颈带来的时间代价远大于此函数。
补码加/减法
word madd(word a, word b){
return a + b;
}
word msub(word a, word b){
return a - b;
}
……没错,就是这么简单,补码就是这么方便。
附录:源代码
complement.c
Compile Mode : C99
#include <stdio.h>
#include <stdlib.h>
typedef unsigned int word;
word atom(char* str){
word res = 0, flag = 0;
for(word i = 0; str[i]; i++){
if('0' <= str[i] && str[i] <= '9')
res = 10 * res + str[i] - '0';
else if(i == 0 && str[i] == '-')
flag = 1; // negative flag
else break; // illegal character
}
// if res != 0 and negative flag
// consider about the input "-0"
if(res && flag)
res = ~res + 1;
return res;
}
char* mtoa(word w){
char* res = (char*) malloc(sizeof(char) * 12);
word flag = w >> 31; // get the topest digit
word cur = 0;
if(flag) { // if negative
res[cur++] = '-';
w = ~w + 1;
}
word startCur = cur;
while(w){
res[cur++] = w % 10 + '0';
w /= 10;
}
word endCur = cur - 1;
// let's reverse
while(startCur < endCur){
char temp = res[startCur];
res[startCur++] = res[endCur];
res[endCur--] = temp;
}
if(cur == 0)
res[cur++] = '0'; // if 0
res[cur] = '\0';
return res;
}
word madd(word a, word b){
return a + b;
}
word msub(word a, word b){
return a - b;
}
int main(){
int t, x, y;
char S[1000], X[100], Y[100];
while(scanf("%d", &t))
switch(t){
case 0:
scanf("%s", S);
puts(mtoa(atom(S)));
break;
case 1:
scanf("%s%s", X, Y);
puts(mtoa(madd(atom(X), atom(Y))));
break;
case 2:
scanf("%s%s", X, Y);
puts(mtoa(msub(atom(X), atom(Y))));
break;
default:
return 0;
}
}
结语
数学真有趣。
- 快速傅里叶变换。 ↩