您的位置:首页 > 其它

从二进制补码到十进制补码及其内的运算——关于补码的一点学习

2009-04-18 21:58 253 查看
问题:输入两个整数a,b,-10100<a, b < 10100, 编程计算a-b。
这是个非常简单的题目,因为a,b可能为正可能为负,所以有a-b, a-(-b), -a-b,
-a-(-b)这四种情况。去括号后有a-b,
-(a-b), a+b, -(a+b),其中共两种情况。写代码的话仍然比较烦。想到二进制的补码运算可以把减法变成加法从而统一加减运算,于是考虑实现十进制的补码运算。十进制的补码运算以及各种进制的运算(做这个题目时查看TAOCP第二卷时学到了关于任意进制的一些东西,包括b进制和bk进制的转换,尤其是-10进制,sqrt(2)进制等等,非常有趣,附在后面)在网上资料非常多,这里只是记下自己做这个题目的过程,备忘。
二进制的补码运算是非常熟悉的了。(X+Y)补=X补+Y补,(X-Y)补=X补+(-Y)补,这是补码能统一加减运算的公式,对于十进制也是一样。对于任何进制的补码,都有公式:
[X]补=M+X(MOD M)
有了这些,将补码应用于十进制就非常简单。自己当时有些偏执,一定想找两个数放在符号位上,替代二进制中0正1负的作用。因为二进制中,0表示什么都没有,1表示再放一个就会溢出,所以十进制中,用0取代二进制中的0,放在符号位上表示正,用9取代二进制中的1,放在符号位上表示负(其实自己把问题弄复杂了,大可不必如此)。这样,包括符号位在内的n个十进制位补码能表示的范围是[-10n-1, 10n-1-1]总共10n个数。例如,计算-46-37这个式子:-46的原码就是946,补码按位取反末位加1,得到954,-37的原码为937,补码为963,补码相加得(-46)补+(-37)补=954+963=1917,符号位进位舍掉,得到结果是917,917的再按位取反末位加1得原码983,它表示的真值是-83,所以有-46-37=-83。
以上如果想计算-99-99就会溢出,如此必须用四位十进制位表示。但一个字节对计算机来说九牛一毛,所以到这里直接就写程序了。代码如下:
#include <stdio.h>
#include <string.h>
char a[128], b[128], ta[128], tb[128];
int main() {
int i, j;
scanf("%s%s", a,b);
if(*a == '-') {
ta[103] = 9;
for(i = 0, j = strlen(a) - 1; j >= 1; i++, j--)
ta[i] = 9 - (a[j] - '0');
while(i <= 102) { ta[i] = 9; i++; }
ta[0] += 1;
} else {
ta[103] = 0;
for(i = 0, j = strlen(a) - 1; j >= 0; i++, j--)
ta[i] = a[j] - '0';
}
if(*b == '-') {
tb[103] = 0;
for(i = 0, j = strlen(b) - 1; j >= 1; i++, j--)
tb[i] = b[j] - '0';
} else {
tb[103] = 9;
for(i = 0, j = strlen(b) - 1; j >= 0; i++, j--)
tb[i] = 9 - (b[j] - '0');
while(i <= 102) { tb[i] = 9; i++; }
tb[0] += 1;
}
for(i = 0; i <= 103; i++) {
tb[i] += ta[i];
tb[i + 1] += tb[i] / 10;
tb[i] %= 10;
}
// print
if(tb[103] == 0) {
for(i = 102; tb[i] == 0; i--);
while(i >= 0) { putchar(tb[i] + '0'); i--; }
putchar('/n');
} else {
for(i = 0; i <= 102; i++)
tb[i] = 9 - tb[i];
tb[0] += 1;
for(i = 0; tb[i] >= 10; i++) {
tb[i+1] += tb[i] / 10;
tb[i] %= 10;
}
putchar('-');
for(i = 102; tb[i] == 0; i--);
while(i >= 0) { putchar(tb[i] + '0'); i--; }
putchar('/n');
}
return 0;
}


这段代码只是为了AC。
现在小结一下。二进制补码定义形式非常漂亮,因为它具有对称性。在二进制里面,很多的东西都是“恰好如此”。恰好二进制只有0、1两个数,恰好只有正负两种状态,恰好2n+2n=2n+1……。在补码的对应规则下,n个数位能表示的数的个数还是那么多,只不过把其中的一半的正数的形式让给了负数。在这个意义下再看十进制的补码,如果是n位十进制数,它能表示10n个十进制数字串,如果使用补码,它必须分一半出来用于表示负数,所以它只能表示0.5 * 10n = 5 * 10n-1个正数。另外的一半就是负的了。我们常说的在补码表示中“符号位参与运算”这个优点,其实并没有什么,只不过权值最高的数位恰好同时能够表示一个数的正负而已,它本来就是应该而且必须参与到运算中去的。
模仿二进制补码的形式定义十进制的补码如下:对于定点整数n+1位的十进制定点整数X=XnXn-1…X0,其补码为:
[X]补=X, 如果0 <= X <=
5 * 10n-1,
[X]补=10n+1 + X = 10n+1 - | X |,如果 -5 * 10n <= X
<= -1
当最高位Xn为0,1,2,3,4时,表示正,为5,6,7,8,9时,表示负。例如三位十进制数补码能表示-500到499这1000个数。例如下列计算:
-327+164: -327的补码是673,-327à673, +164à164,
-327+164à673+164=837, 837是负数,并且它是补码形式,它的绝对值的真值是163.所以-327+164=-163。
当运算结果的绝对值大于500时,会发生溢出。可以使用类似计算机组成原理中的双符号位检测是上溢还是下溢,但通常没有必要,不如把多出来的符号位用来参加计算,使得能够表示的数的范围扩大十倍,以前的溢出自然就没有了。
下面是读到Knuth的TAOCP第二卷的这一节时了解到的其它十分有趣的知识。自己只是走马观花看了一下,把记下来的记在这里:一,b进制和bk进制的互相转换非常简单,看2进制和8进制、16进制的互相转换就明白了。二,-10进制和-2进制是被发现非常有意思的。-2进制似乎也得到了应用。其它比如sqrt(2)i进制等等,非常神奇。这些进制的定义和其它进制一样,只不过每个数位上的权不一样了。例如,要把126这个十进制数转变成-10进制,还是那样除基取余的方法即可,126 / (-10) = -12……6,
-12 / (-10) = 2……8,所以十进制的126在-10进制下的表示是286.可以验算一下,2 * (-10)2 + 8 *
(-10)1 + 6 = 126.不过-10进制数的运算规则自己还没学会。
关于原码转变成补码的方法,在网上都能找到证明。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: