數據在計算機中以二進制串存儲,這種01序列叫“機器數”。
每個機器數都有對應的值,比如0001換算就是現實里十進制的1,這個值叫它的“真值”。
1.原碼
為了表示負數的前面的符號,一種辦法是采用“最高一位表示符號而非數值”的編碼方式——原碼。
如八位的運算器,機器數0000 0001的真值為1,1000 0001的真值為-1。第一位為符號位,剩下的位表示真值。
解決了符號表示問題,但是引出了新的問題,那就是1000 0001不能按照常識意義上來換算了,按正常二進制轉十進制的換算,1000 0001實際上代表129。
這樣在硬件電路設計時會帶來麻煩,要讓機器能夠先辨識符號位,再進行真值的計算。
2.反碼
任何減法都可以轉化為加上一個負數,如 1+1 = 1+(-1)。做加法比減法簡單,要是能用一種新的編碼方法把-1表示出來,然后和+1做加法,最后結果按照這種編碼方式還是正確的,那就太好了。於是為了讓符號位也能參與計算,反碼誕生了。
反碼的表示方法基於原碼,最高位仍然是符號位。正數的反碼是其本身,負數的反碼符號位不變,剩下的位全部取反。
+1 原碼0000 0001,反碼 0000 0001
-1 原碼1000 0001,反碼1111 1110
人們發現,采用這種編碼方式,算出來的值是正確的。
用反碼計算:
1-1 = 1+(-1) = 0000 0001 + 1111 1110 = 1111 1111
而1111 1111的反碼是1000 0000,即 -0。
采用反碼的編碼方式看似解決了符號位參與運算的問題,但仍存在一個明顯的問題,那就是0的表示不唯一。
0000 0000代表 +0,1000 0000代表 -0,這是自原碼就有的問題,是沒有意義的。
你會發現——
用原碼表示,8位表示的機器數的范圍是 [1111 1111, 0111 1111]即[-127,127]。
[-127,127]按現實意義理解的話其實只有255個數,而8個位能理論上表示2^8 = 256個數,少掉的那一位就是用來表示0的正負號了。
3.補碼
人們希望有一種更好的編碼方式,既能解決符號參與運算的問題,又能讓0的表示唯一,由此誕生了補碼。
補碼是一種基於取模同余的模運算系統的編碼方法,這樣說有點復雜,實際上用鍾表來理解很容易。
時鍾的最上方從0開始,順時針依次為1,2,3……直到12。12同時也是0,時鍾表示了0到11總共12個數。

在這個系統中,計算這一行為就可以抽象為撥動指針。
比如我想做加法計算,如1+1,自然等於2,那也可以理解為:指針在1,我順時針撥一格,停在了2。減法運算1-1,就是時針在1,逆時針撥一格,結果為0。
加法就是順時針,減法就是逆時針,最后指針停在的位置就是結果。
那么,從6撥到4,有兩種撥法,要么順時針撥10格,要么逆時針撥2格(當然順時針撥22格也是可以的,也就是多轉一圈)。即 6+10 和 6-2 的結果是等價的。或者說在一個計算式中, +10 和 -2 是等價的。
6 + 10 = 16,6 - 2 = 4,何以等價呢?只要讓:16對12取模(16除以12得出的余數),結果就是4了。
該規律對於系統內的任何加減運算都是適用的,通過取模同余的方式,把減法轉換成了加法,即 -2 可以通過 +10 再對 12 取模來計算。
-1 = +11 mod 12
-2 = +10 mod 12
-3 = +9 mod 12
不難發現,左邊負數的絕對值和右邊整數的和總是12,時鍾是一個模12運算系統。
我們稱 -1 的補碼是 +11 、-2 的補碼是 +10、-3的補碼是+9,即一個負數的補碼是模運算系統的模減去它的絕對值。
有了補碼的概念,1-1 的意思就可以轉述為 1 加上 1 的補碼,即正數減去負數的計算,轉化為該正數加上負數的補碼。
4.補碼的深入
一個負數的補碼 = 模運算系統的模減去它的絕對值
照這么說,一個4位的運算器可以表示 2^4 = 16 個數,把它視為一個模16運算系統的話——
4位運算器負數的補碼 = 16 - 該負數的絕對值
那么設有一個負數 -x,上式就轉化為:
-x[補] = 2^4 - x
舉個例子,-1的補碼就是16-1 = 15,15表示為機器數就是1111。-2的補碼是14,表示為機器數就是1110。-8的補碼是8,表示為機器數就是1000。
按照這種編碼方式,把4位模運算系統表示的16個機器數,也畫成一個時鍾的樣子,如下。

16個數划分為了兩半,一半表示正數(0算在內),一半表示負數。
頂頭0000代表0,底下1000代表-8。0001到0111表示了0到7。1000到1111表示了-8到-1。
可以看到,0的表示是唯一的。
另外,假如有一減法,4 - 4。
4 - 4 = 4 + 4[補] = 4 + (2^4 - 4) = 4 + 12 = 0100 + 1100 (機器數) = 0000
0100 與 1100 相加的結果是 1 0000,因為是4位運算器,最高一位會被舍去,保留下來的就是0000即0。
可見,補碼的編碼方式不僅解決了0表示不唯一的問題(多出來的一位還能多表示一個負數),還方便了硬件的設計(把減法轉化為加法),解決了符號參與計算的問題。故現代計算機普遍采用補碼的編碼方式。
一樣不難發現,划分成了正數一半(0算在內)、負數一半,最高一位其實仍然表示了符號。
1000之前都是正數,除去最高位的后三位表示了機器數的真值,從1000開始變成負數,這是和原碼反碼一樣的特性。
綜上,因此有兩個結論。
1.假設有n位運算器,負數 -x 的補碼為
-x[補] = 2^n - x
減法的計算,等於加上負數的補碼。
2.機器數的最高一位指示了正負號,0為正,1為負,同時也就划分了正數一半(包括0),負數一半。
假設有n位運算器,它用補碼表示的數據范圍為
[-2^(n-1), +2^(n-1)-1]
