C語言中位操作符(1)-計算機中的整數表示方法

寫在前面

長久以來,位操作符一直困擾着我,爲什麼呢?因爲其雖易用,但是我自己卻理解不透徹,用着總覺得有隱患?那麼今天就來詳細地理一下計算機中的位操作符與整數在計算機中的存儲。
本文是作爲一個非科班出身程序員的自我學習記錄之作,如果能夠在自我提高的同時也能幫助讀友提高,我自是高興;如有任何疑問或者不妥之處,敬請評論指正。

寫着寫着有些太多,故而分兩次寫完吧,本次只討論計算機中整數的表示方法。


計算機中數字的表示方法

我們都知道在計算機中,數是以補碼錶示的,這樣表示定義是什麼呢,而好處又是什麼?

先看定義
正數的原碼、反碼、補碼是其本身;
負數的原碼是其本身,反碼是除了符號位以外按位取反,補碼則是反碼加1;

舉例說明

例1

//uint8_t即是無符號8位數,也就是跟char相同的定義
/*0000 1000,這是其原碼反碼和補碼,也就是其在計算機中是這麼存儲的,也就是說計算機中某個位置一定有0000 1000這一段二進制碼*/
uint8_t x = 0x08;

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

這裏x的二進制表示爲0000 1000,最高符號位爲0,表示是正數。8位無符號數8的原反補碼如下:

原碼:0000 1000 
反碼:0000 1000 
補碼:0000 1000

正數的原碼反碼補碼都是其本身。


例2

int8_t x = -0x08;//int8_t即是有符號8位數,其最高位爲符號位
int8_t y = x >> 1;//x = 1000 1000 -> y = 1000 0100 = -4;
uint8_t z = x >> 1;

8位有符號數-8,其二進制表示爲1000 1000,最高位1表示符號位,表示負數,那麼其原碼反碼補碼爲:

原碼:1000 1000 
反碼:1111 0111 
補碼:1111 1000

負數的原碼是其本身,反碼是除了符號位外按位取反,補碼是反碼加1.


爲什麼使用補碼?

由上面的例子就知道了正數與負數的原反補碼的表示方法。那麼隨之而來的一個問題是,爲什麼不用直觀的原碼來表示數,而用繁瑣的補碼?
在網上查了很多,大致歸結爲如下兩個原因:
最主要原因,使用補碼可以將符號位和其他位進行統一處理,同時減法也可以按加法來進行運算。
這樣帶來了兩個好處:符號位統一到計算中去和減法可以按加法來計算,而這兩個好處都使得CPU設計更容易。
例3
加入考慮符合位的話,那麼很明顯CPU的加減法設計要變複雜,不僅要單獨處理符號位還要判斷除了符號位之外的大小,並且還要考慮溢出時符號位不被沖掉。
那麼假設符號位不單獨考慮呢?

//-8 + 15 = 7, 8-bit depth operation
//原碼運算
    1000 1000
  + 0000 1111
  = 1001 0111 //-23
//反碼運算,
    1111 0111
 +  0000 1111
 = 10000 1000 //8,第一位截斷丟掉
 // 補碼運算
    1111 1000
 +  0000 1111
 = 10000 0111//7,第一位丟掉

那麼由上面可以看到,在符號位納入計算的情況下,只有補碼計算的結果是正確的,並且可以將減法按加法計算。
那麼不禁要問,爲什麼要這麼設計補碼呢?
這裏涉及到補碼的本質的問題。
那麼首先,-8,它就是0-8的結果,按8-bit 2進製表示如下

 //87654 3210//bit
   11111     //表示借位,與10進制相同,只不過2進制中借1位表示2
    0000 0000
  - 0000 1000
  =11111 1000

由於我們要向更高位(8th-bit借位),並且在結果中會丟掉超出範圍的位(上面結果中最高位的1),那麼上述結果其實等於1 0000 0000 - 0000 1000,即

 //87654 3210//bit
   11111     //表示借位,與10進制相同,只不過2進制中借1位表示2
   10000 0000
  - 0000 1000
  =01111 1000

當丟掉最高位後,那麼1 0000 0000 - 0000 1000 其實與0000 0000 - 0000 1000 的結果是一樣的!
而1 0000 0000 = 1111 1111 + 1.那麼求-8也就變成了1111 1111 - 0000 1000 + 1,也即是:

            1
    1111 1111
  - 0000 1000
  = 1111 0111//在該結果上加1,即得1111 1000

在二進制中,如果用w-bit全1的一個值(2w1 )減去一個w-bit的二進制數x,那麼結果其實就是對x按位取反(如上)。
所以負數的補碼就是對其除符號位外按位取反再加1。其實對於補碼,另外的一個求法跟這個是等價的,就是一個負數-x(x>0)的補碼就是對x按位取反(包括符號位),再加1。因爲x是正的,其符號位肯定是0,那麼按位取反後就是1(表示負的)。
那麼這裏再說負數補碼的本質,-x(x>0)在w-bit下的補碼就是2wx 的一個快速算法而已。

爲什麼補碼適用於正數的加法?

那麼還有一個疑問,補碼爲什麼會適用於正數的加法呢?

實際上,我們要證明的是,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 ]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章