天天看点

C语言中位操作符(1)-计算机中的整数表示方法

写在前面

长久以来,位操作符一直困扰着我,为什么呢?因为其虽易用,但是我自己却理解不透彻,用着总觉得有隐患?那么今天就来详细地理一下计算机中的位操作符与整数在计算机中的存储。
本文是作为一个非科班出身程序员的自我学习记录之作,如果能够在自我提高的同时也能帮助读友提高,我自是高兴;如有任何疑问或者不妥之处,敬请评论指正。
           

写着写着有些太多,故而分两次写完吧,本次只讨论计算机中整数的表示方法。

计算机中数字的表示方法

我们都知道在计算机中,数是以补码表示的,这样表示定义是什么呢,而好处又是什么?

先看定义:

正数的原码、反码、补码是其本身;

负数的原码是其本身,反码是除了符号位以外按位取反,补码则是反码加1;

举例说明:

例1

//uint8_t即是无符号8位数,也就是跟char相同的定义
/*0000 1000,这是其原码反码和补码,也就是其在计算机中是这么存储的,也就是说计算机中某个位置一定有0000 1000这一段二进制码*/
uint8_t x = ;

uint8_t y = x >> ;//x = 0000 1000 -> y = 0000 0100 = 4;
           

这里x的二进制表示为0000 1000,最高符号位为0,表示是正数。8位无符号数8的原反补码如下:

原码:0000 1000 
反码:0000 1000 
补码:0000 1000
           

正数的原码反码补码都是其本身。

例2

int8_t x = -;//int8_t即是有符号8位数,其最高位为符号位
int8_t y = x >> ;//x = 1000 1000 -> y = 1000 0100 = -4;
uint8_t z = x >> ;
           

8位有符号数-8,其二进制表示为1000 1000,最高位1表示符号位,表示负数,那么其原码反码补码为:

原码:1000 1000 
反码:1111 0111 
补码:1111 1000
           

负数的原码是其本身,反码是除了符号位外按位取反,补码是反码加1.

为什么使用补码?

由上面的例子就知道了正数与负数的原反补码的表示方法。那么随之而来的一个问题是,为什么不用直观的原码来表示数,而用繁琐的补码?

在网上查了很多,大致归结为如下两个原因:

最主要原因,使用补码可以将符号位和其他位进行统一处理,同时减法也可以按加法来进行运算。

这样带来了两个好处:符号位统一到计算中去和减法可以按加法来计算,而这两个好处都使得CPU设计更容易。

例3

加入考虑符合位的话,那么很明显CPU的加减法设计要变复杂,不仅要单独处理符号位还要判断除了符号位之外的大小,并且还要考虑溢出时符号位不被冲掉。

那么假设符号位不单独考虑呢?

//-8 + 15 = 7, 8-bit depth operation
//原码运算
     
  +  
  =   //-23
//反码运算,
     
 +   
 =   //8,第一位截断丢掉
 // 补码运算
     
 +   
 =  //7,第一位丢掉
           

那么由上面可以看到,在符号位纳入计算的情况下,只有补码计算的结果是正确的,并且可以将减法按加法计算。

那么不禁要问,为什么要这么设计补码呢?

这里涉及到补码的本质的问题。

那么首先,-8,它就是0-8的结果,按8-bit 2进制表示如下

//87654 3210//bit
   11111     //表示借位,与10进制相同,只不过2进制中借1位表示2
    0000 0000
  -  
  = 
           

由于我们要向更高位(8th-bit借位),并且在结果中会丢掉超出范围的位(上面结果中最高位的1),那么上述结果其实等于1 0000 0000 - 0000 1000,即

//87654 3210//bit
   11111     //表示借位,与10进制相同,只不过2进制中借1位表示2
   10000 0000
  -  
  = 
           

当丢掉最高位后,那么1 0000 0000 - 0000 1000 其实与0000 0000 - 0000 1000 的结果是一样的!

而1 0000 0000 = 1111 1111 + 1.那么求-8也就变成了1111 1111 - 0000 1000 + 1,也即是:

1
    1111 1111
  -  
  =  //在该结果上加1,即得1111 1000
           

在二进制中,如果用w-bit全1的一个值( 2w−1 )减去一个w-bit的二进制数x,那么结果其实就是对x按位取反(如上)。

所以负数的补码就是对其除符号位外按位取反再加1。其实对于补码,另外的一个求法跟这个是等价的,就是一个负数-x(x>0)的补码就是对x按位取反(包括符号位),再加1。因为x是正的,其符号位肯定是0,那么按位取反后就是1(表示负的)。

那么这里再说负数补码的本质,-x(x>0)在w-bit下的补码就是 2w−x 的一个快速算法而已。

为什么补码适用于正数的加法?

那么还有一个疑问,补码为什么会适用于正数的加法呢?

实际上,我们要证明的是,X-Y或X+(-Y)可以用X加上Y的2的补码完成。

Y的2的补码等于(11111111-Y)+1。所以,X加上Y的2的补码,就等于:

X + (11111111-Y) + 1

我们假定这个算式的结果等于Z,即 Z = X + (11111111-Y) + 1

接下来,分成两种情况讨论。

第一种情况,如果X小于Y,那么Z是一个负数。这时,我们就对Z采用2的补码的逆运算,求出它对应的正数绝对值,再在前面加上负号就行了。所以,

Z = -[11111111-(Z-1)] = -[11111111-(X + (11111111-Y) + 1-1)] = X - Y

第二种情况,如果X大于Y,这意味着Z肯定大于11111111,但是我们规定了这是8位机,最高的第9位是溢出位,必须被舍去,这相当于减去100000000。所以,

Z = Z - 100000000 = X + (11111111-Y) + 1 - 100000000 = X - Y

这就证明了,在正常的加法规则下,可以利用2的补码得到正数与负数相加的正确结果。换言之,计算机只要部署加法电路和补码电路,就可以完成所有整数的加法

本段证明摘自 [ 阮一峰-关于2的补码 ]

参考文献:

  1. [ 阮一峰-关于2的补码 ]
  2. [ Two’s Complement-Cornell ]

继续阅读