3. 整數的加減運算

我們已經瞭解了計算機中正整數如何表示,加法如何計算,那麼負數如何表示,減法又如何計算呢?本節討論這些問題。為了書寫方便,本節舉的例子都用8個bit表示一個數,實際計算機做整數加減運算的操作數可以是8位、16位、32位甚至64位的。

3.1. Sign and Magnitude表示法

要用8個bit表示正數和負數,一種簡單的想法是把最高位規定為符號位(Sign Bit),0表示正1表示負,剩下的7位表示絶對值的大小,這稱為Sign and Magnitude表示法。例如-1表示成10000001,+1表示成00000001。這樣用8個bit表示整數的取值範圍是-27-1~27-1,即-127~127。

採用這種表示法,計算機做加法運算需要處理以下邏輯:

  1. 如果兩數符號位相同,就把它們的低7位相加,符號位不變。如果低7位相加時在最高位產生進位,說明結果的絶對值大於127,超出7位所能表示的數值範圍,這稱為溢出(Overflow)[24],這時通常把計算機中的一個標誌位置1表示當前運算產生了溢出。

  2. 如果兩數符號位不同,首先比較它們的低7位誰大,然後用大數減小數,結果的符號位和大數相同。

那麼減法如何計算呢?由於我們規定了負數的表示,可以把減法轉換成加法來計算,要計算a-b,可以先把b變號然後和a相加,相當於計算a+(-b)。但如果兩個加數的符號位不同就要用大數的絶對值減小數的絶對值,這一步減法計算仍然是免不了的。我們知道加法要進位,減法要借位,計算過程是不同的,所以除了要有第 1 節 “為什麼計算機用二進制計數”提到的加法器電路之外,還要另外有一套減法器電路。

如果採用Sign and Magnitude表示法,計算機做加減運算需要處理很多邏輯:比較符號位,比較絶對值,加法改減法,減法改加法,小數減大數改成大數減小數……這是非常低效率的。還有一個缺點是0的表示不唯一,既可以表示成10000000也可以表示成00000000,這進一步增加了邏輯的複雜性,所以我們迫切需要重新設計整數的表示方法使計算過程更簡單。

3.2. 1's Complement表示法

本節介紹一種二進制補碼表示法,為了便於理解,我們先看一個十進制的例子:

167-52=167+(-52)=167+(999-52)-1000+1=167+947-1000+1=1114-1000+1=114+1=115
167-52 → 減法轉換成加法 167+(-52) → 負數取9的補碼表示 167+947 → 114進1 → 高位進的1加到低位上去,結果為115

在這個例子中我們用三位十進制數字表示正數和負數,具體規定如下:

表 14.5. 9's Complement表示法

數值補碼表示
-499500
-498501
......
-1998
0999
00
11
......
498498
499499

首先-52要用999-52表示,就是947,這稱為取9的補碼(9's Complement);然後把167和947相加,得到114進1;再把高位進的1加到低位上去,得115,本來應該加1000,結果加了1,少加了999,正好把先前取9的補碼多加的999抵消掉了。我們本來要做167-52的減法運算,結果變成做999-52的減法運算,後者顯然要容易一些,因為沒有借位。這種補碼表示法的計算規則用一句話概括就是:負數用9的補碼表示,減法轉換成加法,計算結果的最高位如果有進位則要加回到最低位上去。要驗證這條規則得考慮四種情況:

  1. 兩個正數,相加得正

  2. 一正一負,相加得正

  3. 一正一負,相加得負

  4. 兩個負數,相加得負

我們舉的例子驗證了第二種情況,另外三種情況請讀者自己驗證,暫時不考慮溢出的問題,稍後會講到如何判定溢出。

上述規則也適用於二進制:負數用1的補碼(1's Complement)表示,減法轉換成加法,計算結果的最高位如果有進位則要加回到最低位上去。取1的補碼更簡單,連減法都不用做,因為1-1=0,1-0=1,取1的補碼就是把每個bit取反,所以1的補碼也稱為反碼。比如:

00001000-00000100 → 00001000+(-00000100) → 00001000+11111011 → 00000011進1 → 高位進的1加到低位上去,結果為00000100

1's Complement表示法相對於Sign and Magnitude表示法的優勢是非常明顯的:不需要把符號和絶對值分開考慮,正數和負數的加法都一樣算,計算邏輯更簡單,甚至連減法器電路都省了,只要有一套加法器電路,再有一套把每個bit取反的電路,就可以做加法和減法運算。如果8個bit採用1's Complement表示法,負數的取值範圍是從10000000到11111111(-127~0),正數是從00000000到01111111(0~127),仍然可以根據最高位判斷一個數是正是負。美中不足的是0的表示仍然不唯一,既可以表示成11111111也可以表示成00000000,為瞭解決這最後一個問題,我們引入2's Complement表示法。

3.3. 2's Complement表示法

2's Complement表示法規定:正數不變,負數先取反碼再加1。如果8個bit採用2's Complement表示法,負數的取值範圍是從10000000到11111111(-128~-1),正數是從00000000到01111111(0~127),也可以根據最高位判斷一個數是正是負,並且0的表示是唯一的,目前絶大多數計算機都採用這種表示法。為什麼稱為“2的補碼”呢?因為對一位二進制數b取補碼就是1-b+1=10-b,相當於從2里面減去b。類似地,要表示-4需要對00000100取補碼,11111111-00000100+1=100000000-00000100,相當於從28裡面減去4。2's Complement表示法的計算規則有些不同:減法轉換成加法,忽略計算結果最高位的進位,不必加回到最低位上去。請讀者自己驗證上一節提到的四種情況下這條規則都能算出正確結果。

8個bit採用2's Complement表示法的取值範圍是-128~127,如果計算結果超出這個範圍就會產生溢出,例如:

圖 14.3. 有符號數加法溢出

有符號數加法溢出

如何判斷產生了溢出呢?我們還是分四種情況討論:如果兩個正數相加溢出,結果一定是負數;如果兩個負數相加溢出,結果一定是正數;一正一負相加,無論結果是正是負都不可能溢出。

圖 14.4. 如何判定溢出

如何判定溢出

從上圖可以得出結論:在相加過程中最高位產生的進位和次高位產生的進位如果相同則沒有溢出,如果不同則表示有溢出。邏輯電路的實現可以把這兩個進位連接到一個異或門,把異或門的輸出連接到溢出標誌位。

3.4. 有符號數和無符號數

前面幾節我們用8個bit表示正數和負數,講了三種表示法,每種表示法對應一種計算規則,這稱為有符號數(Signed Number);如果8個bit全部表示正數則取值範圍是0~255,這稱為無符號數(Unsigned Number)。其實計算機做加法時並不區分操作數是有符號數還是無符號數,計算過程都一樣,比如上面的例子也可以看作無符號數的加法:

圖 14.5. 無符號數加法進位

無符號數加法進位

如果把這兩個操作數看作有符號數-126和-8相加,計算結果是錯的,因為產生了溢出;但如果看作無符號數130和248相加,計算結果是122進1,也就是122+256,這個結果是對的。計算機的加法器在做完計算之後,根據最高位產生的進位設置進位標誌,同時根據最高位和次高位產生的進位的異或設置溢出標誌。至于這個加法到底是有符號數加法還是無符號數加法則取決於程序怎麼理解了,如果程序把它理解成有符號數加法,下一步就要檢查溢出標誌,如果程序把它理解成無符號數加法,下一步就要檢查進位標誌。通常計算機在做算術運算之後還可能設置另外兩個標誌,如果計算結果的所有bit都是零則設置零標誌,如果計算結果的最高位是1則設置負數標誌,如果程序把計算結果理解成有符號數,也可以檢查負數標誌判斷結果是正是負。



[24] 有時候會進一步細分,把正整數溢出稱為上溢(Overflow),負整數溢出稱為下溢(Underflow),詳見strtol(3)