原碼, 反碼和補碼.
原碼表示方式(最高位作為符號位, 0 表示正數, 1 表示負數):
反碼表示方式(負整數就是正整數按位取反):
補碼表示方式(負整數是正整數按位取反再加 1):
不妨假設只有 4 個二進制位用來表示整數, 二進制從 0000 到 1111, 一共能表示 2 ^ 4 即 16 個整數. 把它們放到一個有 16 個刻度的表盤上:
圖 1
圖1 所示, 由於只有 4 個二進制位, 因此只有 16 種 位模式, 表盤上 16 個刻度每個刻度對應於一個 位模式. 很顯然每個位模式可以用來對應表示一個整數. 而如果它們都用來表示正整數的話, 只能表示 0 到 15.
那怎麼表示負數呢?
為了公平起見, 正數負數各分一半的刻度. 不妨將這個表盤切成兩半, 一半表示正數, 一半表示負數. 如果我們規定最高位表示符號的正負, 那麼能表示數值的位就只剩下 3 個二進制位, 3 個二進制位表示數值只能表示 8 個, 從 這正是原碼表示整數的方法:
圖 2
圖 2 所示. 這種表示方式在"規定"上看來很自然: 最高位是符號位, 剩下的是數值位. 其實不然: 原來用於表示正整數 8 的位模式 1000 現在用來表示 -0. 原來用來表示正整數 15 的位模式 1111 現在用來表示 -7. 如果我說, 在只有 4 個二進制位的情況下, 使用原碼來表示整數(有正有負)時, 8 ==> -0, 15 ==> -7, 11 ==> -3, ...(而不是告訴你最高位作為符號位什麼的, 忘掉符號位的規定), 你會不會(像補碼表示一樣)感覺到有點 "make no sense" 呢.
在原碼表示法中, 0 有兩種表示, 0000 和 1000, 即 +0 和 -0.
想法是一樣的: 把表盤切開, 各分一半. 只不過這種各分一半的分法是直接將 位模式 取反 -- 每個位模式都有一個相反的位模式不是麼:
圖 3
圖3 所示. 原本用來表示正整數 15 的位模式 1111 現在用於表示 -0. 原本用來表示正整數 8 的位模式現在用於表示 -7. 同樣, 在反碼表示法中, 0 有兩種表示, 0000 和 1111, 即 +0 和 -0.
one's complement. 為什麼叫 one's complement 呢?
. 如果用另外一種方式來表示取反過程的話:
我們發現對一個數 n 取反的操作等價於用所有位都為 1 的數減去 n. 再看看反碼表示法中, 1111 這個位模式用來表示多少呢, 表示 . 再看將 的位模式 取反後得到的位模式 用來表示多少呢? 也就是說如果我們使用 one's complement 這種表示法的話, 可以很自然的(通過位模式相減)得到 -0 - 5 == -5 這樣的等式, 這是使用原碼表示法所做不到的事情哦!(比如原碼的 -0為 1000, 5 為 0101, 1000 - 0101 == 0011, 而 0011 在原碼中表示 3). 然而由於在反碼表示法中 0 有 +0 和 -0 兩種表示, 我們可以得到 -0 - 5 == -5 這個等式, 卻得不到 +0 - 5 == -5 的等式.
圖 4
圖4 所示, 這就是補碼表示法啦. 原本用於表示正整數 15 的位模式 1111 用於表示 -1. 原本用於表示正整數 8 的位模式 1000 現在用於表示 -8. 0 只有一種表示方式, 就是 0000. 即在這種表示法中, 15 ==> -1, 8 ==> -8, 12 ==> -4, 這個突然就 "make no sense" 了? 那對原碼表示法為什麼沒有突然 "make no sense" 呢... 大約因為某個扯淡"定理"比較容易讓人腦子短路的原因吧...
two's complement. 原因很簡單, 同樣以 +5 為例:
表示 +5, 位模式 表示 . 位模式 (共 5 位) 呢? 它無法在我們的表盤上表示. 然而如果我們非要在表盤上表示它的話, 那麼我希望你能理解, (可以想象一個時針, 每向前走一個刻度表示 +1, 在這個表盤上, 當它到達 16 時又歸零. 實際上就是 % 16. (mod 16, 模 16 運算)).
在補碼表示中, 我們很自然地(通過位模式相減)得到了 0 - 5 == -5. 由於補碼表示法中表示 0 的方法只有一種, 所以它沒有反碼的 +0 和 -0 的問題.
"一個負整數的補碼表示 等於將 其所對應的正整數的補碼表示 取反再加一" 這個扯淡 "定理".
+5 的補碼表示為 , 則 的補碼表示為 (~ 表示取反, C 語言中的按位取反運算符).
1111 就是補碼表示中的 -1. 補碼表示中的 -1 是通過位模式 0000 - 0001 得到的(忽略借的一位, 那位在表盤上是不可表示的).
這個"定理"了.
圖 5
圖5 所示. 時針從 走到 (如果我們堅持認為這個表盤一半的位模式表示負數一半表示正數並采用補碼表示), 或者從 走到 (如果我們認為所有的位模式都用來表示正數), 好吧更明確地說, 可以走兩條路, 逆時針走 格, 或者順時針走 格. 如果逆時針表示減法而順時針表示加法的話, 那麼我們有:
只有 4 位二進制位的情況下的)補碼表示法中, 既然 -4 和 12 的位模式是一樣的, -3 和 13 的位模式也是一樣的, -7 和 9 的位模式也是一樣的, 那這兩種運算實際上沒有差別. 實際上 共模的模是 16.
怎麼解釋內存中的一個位模式究竟是正數? 還是看著負數? 是軟件的事情. 當然計算機在運算過程中同樣會依據運算的結果進行是否進位/借位的置位. 軟件可以通過標志位進行判斷. 由於補碼這種表示形式可以簡化硬件電路的設計, 因此現在的計算機表示整數基本都使用補碼表示形式.
如果用 1 個 Byte 表示一個整數, 那麼其模是 2 ^ 8; 如果用 4 個 Byte 表示一個整數, 那麼其模是 2 ^ 32. 這種情況下只需要畫一個更加大的表盤, 有更精細的刻度就可以了, 其基本解釋和只有 4 位表示一個整數的情況是一樣的.