Walk More Slowly

计算机是用n位0和1来表示数字的,这样很容易表示正数,但是怎么表示负数呢?

人类聪明的大脑想到了用第一位来表示符号,0代表正数,1代表负数。这种表示方法最好理解,叫做原码。

但是计算机在计算的时候,为了简化,需要把减法当做加法运算。这个很简单,负数不就是干这个的吗?比如2-1=2+(-1)。

但是负数如果按照原码表示的话,就不好办了,比如:

2-1=2+(-1)=00000010+10000001=10000011=-3

显然是错的,所以人类习惯的方法无法满足这个需求,根本原因在于符号位的引入不符合计算机运行原理。

那么,肿么办呢?

首先介绍模的概念:数学上7位数可以表示[0, 127]共128个值,超过128就会溢出,那么128就是7位数的模。

而模一定的情况下,减法都可以变换成加法来运算,比如:

你要把钟表往前拨2个小时,你可以把它往前拨10个小时来实现。这里-2就转化为了+10。

下面看同余数的数学定义:

如果a和b对于一个数m的余数相同,成a和b为m的同余数,或者称a和b互为补数,记做:

a ≡ b (mod m)

那么问题来了,负数的余数如何定义?

数学上是

就是:x对y的余数等于x减去y乘上x与y的商的下界。

而显然,一个数的余数一定是它的同余数。

这样如果你想算a-b的话(a,b都是正数),可以算成a+(-b的补数),比如:

模为128的情况下,2-1=2+(-1)=2+(128-1)=128+1,由于溢出,结果是1

给定负数的原码,如何让计算机方便的算出负数的补数?

假如用n位来表示数,那么-a=2^(n-1)-a-2^(n-1),其中a为正数

而a表示成10进制的话,

是a=k0*2^0+k1*2^1+k2*2^2+…k(n-2)*2^(n-2),为什么是n-2呢?因为原码要留一位给符号位,所以只有n-1位有效位。

而2^(n-1)=1+1*2^0+1*2^1+1*2^2+…+1*2^(n-2),所以

2^(n-1)-a=1+(1-k0)*2^0+(1-k1)*2^1+(1-k2)*2^2+…(1-k(n-2))*2^(n-2),也就是除了最高位之外,其他部分取反加1。

而剩下的-2^(n-1),它的补数是2^(n-1),也就是还要再加上个最高位为1其他全为0的数.

那么就得出我们教科书的结论:

对于负数,原码到补码就是:符号位为1,其他位取反加1(不可进位到符号位)。

显然,对于正数,补码等于原码(想想钟表的例子)。

注意一个特例,,-128是没有原码的,因为原码的表示范围就是[-127~127],0有10000000和00000000两个。

但是按补码算的话,-127-1=11111111+10000001=10000000

所以补码是可以表示-128的,因此补码的表示范围是[-128~127]

注意,补码的计算完全是2进制的计算,没有符号位,而且该溢出溢出。

总结:

1. 计算机都是用补码表示数,语言在编译的时候已经把原码转换成了补码,再写入内存。

比如

int i=-1;

printf("%x\n",i);

输出就是0xFFFFFFFF

2. 计算机不知道减法,也不知道符号位,它只知道加法和溢出。所以补码运算只有加法和溢出。

3. print的时候,%d是打印原码,%x是打印计算机里的原始数据。而用0x80000000来赋值的话,其实是直接赋值给计算机内存原始数据。

比如

int i=0x80000000;

printf("%d\n",i);

输出就是-2147483648

风景如何,其实并不重要。重要的是,你在我的身边。

Walk More Slowly

相关文章:

你感兴趣的文章:

标签云: