最近都一直在看幾本關於計算機組成原理方面的大作:《Code: The Hidden Language of Computer Hardware and Software》,《Computer System: A Programer Perspective》,《Introduction to Computer Systems》,算是補充了自己作為一個非計算機專業的程序員在這方面的缺失。特別是看了《Code》中關於加法器與減法器實現的章節后,對於二進制補碼有了一個感性的認識。
在計算機的歷史中,曾經出現過三種表示負數的二進制方法。第一種是直接用一個符號位。第二種是用反碼,即對一個整數的全部位取反則得到這個數的負數。而最后的一種方式就是二進制補碼(Two’s Complement)。
但是為什么現在的計算機都要采用二進制補碼這種方式呢?在所有知識的背后,要做到知其然的同時,更要做到知其所以然,這樣才能做到真正的理解。其實采用二進制補碼的原因很簡單,簡單,高效。
要真正理解這背后的原因,首先我們需要理解,計算機是如何做加法的。
我們知道,當一個與門和一個異或門組合在一起的時候,就可以形成一個接受兩個1bit的輸入,並且輸出其相加的和的結果和進位的半加器。
而當兩個這樣的半加器再與一個或門組合在一起時,就可以形成一個接受兩個1bit輸入,1bit進位輸入,輸出加和輸出和進位輸出的全加器。
而當N個這樣的全加器組合在一齊時,就可以形成一個能計算兩個N為二進制數相加的加法器。
理解了上面的實現方式之后,我們就可以做出一台上古時代用於計算二進制加法的加法器:
當我們實現了加法器之后,理所當然的,我們希望同樣能實現減法。但我們知道減法與加法相比,是不一樣的機制的。加法只有向前進位,而減法呢?減法則有可能會向前借位,同時,前一位又可能再向前借位,這是一種十分麻煩的機制。那么有什么辦法可以解決這個這么麻煩的問題呢?上古時代的天才們就開始想,能不能把減法轉換為加法,那么就可以借用簡單的加法器來進行減法運算呢?於是一種基於補碼的使減法變成加法,通過加法器來運算的方法就誕生了。
首先,可以看一個在《Code》里面舉的例子:
253 – 176 = ???
可以看到,這個減法運算在個位的時候,就需要向前借位了,十分麻煩。
但是我們可以轉變一下思路,首先這是一個三位數,三位數的最大值是999。因此我們先用999減去減數176
999 – 176 = 823
然后,用被減數253加上上面求出來的這個值823
253 + 823 = 1076
然后把這個值加1再減去1000,這樣就得到了77。噢,這個不正是我們需要的結果77嗎?
大家可以看到,在這個過程中,我們雖然用到了減法,但是我們沒有用到借位,並且,若我們把這種方法類比到二進制上,我們就會發現,上面的幾個做減法的步驟,完全可以通過簡單的門電路來實現,而不需要真正地進行運算。好,我們就用同樣上面的數字來進行一下二進制的運算。
253 – 176 轉換為二進制則是:1111 1101 – 1011 0000 = ???? ????
第一步,用8位二進制數中的最大值,即1111 1111,減去減數1011 0000
1111 1111 – 1011 0000 = 0100 1111
然后,用被減數與上面的結果相加:
1111 1101 + 0100 1111 = 1 0100 1100
把上述結果加一再減去9位二進制數的最小值1 0000 0000
1 0100 1100 + 1 = 1 0100 1101
1 0100 1101 – 1 0000 0000 = 0100 1101
得到上述結果0100 1101 = (10)77
我們通過相同的方法在二進制上的使用,求出了同樣的結果77。細心的同學已經可以觀察到,這里需要用到的減法步驟,其實並不需要真正的減法。例如,第一步,用1111 1111 – 1011 0000得到的結果0100 1111,其實就是對1011 0000的取反,而取反,我們其實只需要通過取反器就可以實現了。然后,在最后一步,我們減去1 0000 0000其實我們要做的就是把溢出的那個1去掉就得到我們的結果。
看完了上面的演示,解釋一下這個實現的方式:
首先用十進制來解釋一下,我們用999 – 176 + 253 + 1 - 1000這個式子來得到結果。若我們把這個式子根據運算規律簡單地做一些變換:253 + (999 – 176) + 1 – 1000,可以看到,我們其實就是在加上1000后再減去1000。在此我們把用999減去一個數再加一的這種做法稱為求9的補數。
而對二進制做同樣的轉換后,即對一個數先全部為取反,然后再加一,就得到了一個數的二進制補數。說到這里,我們大概也就明白了為什么我們在計算機中使用補數來表示一個負數的原因了。因為我們知道,減一個正數,其實就是加一個負數。那么我們就可以想一種方法,讓這個負數的表示既容易表示,用符合運算的規律。顯然,只用符號位不行,僅僅取反也不行。而補數則可以。
我想這大概就是計算機中使用補數來表示負數的原因吧。因為即使是現代的計算機處理器中,其加法器的基本實現的方式都是不變的,只是變得更加高效而已,而為了使用這種加法器的簡單性,我們就使用補碼這種方式來復用加法器來計算減法運算。