这周计算机原理课收到楼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)
接下来看一下在0-1串内部的一些位运算:
设有0-1串
按位取反运算:Not(m)在 [0,2n) 中封闭。
设 m≠0 是一个 n 位的0-1串,证明: mtoa(Not(m)+1)=−mtoa(m) 。
证:
代入按位取反公式:
若 m∈(0,2n−1)→2n−m∈(2n−1,2n)
根据补码的定义
若 m∈[2n−1,2n)→2n−m∈(0,2n−1]
根据补码的定义
综上所述,
所以,非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;
}
}
数学真有趣。