本篇文章講解了計算機的原碼, 反碼和補碼. 而且進行了深刻探求了爲什麼要使用反碼和補碼, 以及更進一步的論證了爲什麼能夠用反碼, 補碼的加法計算原碼的減法. 論證部分若有不對的地方請各位牛人幫忙指正! 但願本文對你們學習計算機基礎有所幫助!html
在學習原碼, 反碼和補碼以前, 須要先了解機器數和真值的概念.算法
一個數在計算機中的二進制表示形式, 叫作這個數的機器數。機器數是帶符號的,在計算機用一個數的最高位存放符號, 正數爲0, 負數爲1.編程
好比,十進制中的數 +3 ,計算機字長爲8位,轉換成二進制就是00000011。若是是 -3 ,就是 10000011 。學習
那麼,這裏的 00000011 和 10000011 就是機器數。編碼
由於第一位是符號位,因此機器數的形式值就不等於真正的數值。例如上面的有符號數 10000011,其最高位1表明負,其真正數值是 -3 而不是形式值131(10000011轉換成十進制等於131)。因此,爲區別起見,將帶符號位的機器數對應的真正數值稱爲機器數的真值。設計
例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –13d
在探求爲什麼機器要使用補碼以前, 讓咱們先了解原碼, 反碼和補碼的概念.對於一個數, 計算機要使用必定的編碼方式進行存儲. 原碼, 反碼, 補碼是機器存儲一個具體數字的編碼方式.htm
原碼就是符號位加上真值的絕對值, 即用第一位表示符號, 其他位表示值. 好比若是是8位二進制:blog
[+1]原 = 0000 0001ip
[-1]原 = 1000 0001
第一位是符號位. 由於第一位是符號位, 因此8位二進制數的取值範圍就是:
[1111 1111 , 0111 1111]
即
[-127 , 127]
原碼是人腦最容易理解和計算的表示方式.
反碼的表示方法是:
正數的反碼是其自己
負數的反碼是在其原碼的基礎上, 符號位不變,其他各個位取反.
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
可見若是一個反碼錶示的是負數, 人腦沒法直觀的看出來它的數值. 一般要將其轉換成原碼再計算.
補碼的表示方法是:
正數的補碼就是其自己
負數的補碼是在其原碼的基礎上, 符號位不變, 其他各位取反, 最後+1. (即在反碼的基礎上+1)
[+1] = [00000001]原 = [00000001]反 = [00000001]補
[-1] = [10000001]原 = [11111110]反 = [11111111]補
對於負數, 補碼錶示方式也是人腦沒法直觀看出其數值的. 一般也須要轉換成原碼在計算其數值.
在開始深刻學習前, 個人學習建議是先"死記硬背"上面的原碼, 反碼和補碼的表示方式以及計算方法.
如今咱們知道了計算機能夠有三種編碼方式表示一個數. 對於正數由於三種編碼方式的結果都相同:
[+1] = [00000001]原 = [00000001]反 = [00000001]補
因此不須要過多解釋. 可是對於負數:
[-1] = [10000001]原 = [11111110]反 = [11111111]補
可見原碼, 反碼和補碼是徹底不一樣的. 既然原碼纔是被人腦直接識別並用於計算表示方式, 爲什麼還會有反碼和補碼呢?
首先, 由於人腦能夠知道第一位是符號位, 在計算的時候咱們會根據符號位, 選擇對真值區域的加減. (真值的概念在本文最開頭). 可是對於計算機, 加減乘數已是最基礎的運算, 要設計的儘可能簡單. 計算機辨別"符號位"顯然會讓計算機的基礎電路設計變得十分複雜! 因而人們想出了將符號位也參與運算的方法. 咱們知道, 根據運算法則減去一個正數等於加上一個負數, 即: 1-1 = 1 + (-1) = 0 , 因此機器能夠只有加法而沒有減法, 這樣計算機運算的設計就更簡單了.
因而人們開始探索 將符號位參與運算, 而且只保留加法的方法. 首先來看原碼:
計算十進制的表達式: 1-1=0
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
若是用原碼錶示, 讓符號位也參與計算, 顯然對於減法來講, 結果是不正確的.這也就是爲什麼計算機內部不使用原碼錶示一個數.
爲了解決原碼作減法的問題, 出現了反碼:
計算十進制的表達式: 1-1=0
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
發現用反碼計算減法, 結果的真值部分是正確的. 而惟一的問題其實就出如今"0"這個特殊的數值上. 雖然人們理解上+0和-0是同樣的, 可是0帶符號是沒有任何意義的. 並且會有[0000 0000]原和[1000 0000]原兩個編碼表示0.
因而補碼的出現, 解決了0的符號以及兩個編碼的問題:
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]補 + [1111 1111]補 = [0000 0000]補=[0000 0000]原
這樣0用[0000 0000]表示, 而之前出現問題的-0則不存在了.並且能夠用[1000 0000]表示-128:
(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]補 + [1000 0001]補 = [1000 0000]補
-1-127的結果應該是-128, 在用補碼運算的結果中, [1000 0000]補 就是-128. 可是注意由於其實是使用之前的-0的補碼來表示-128, 因此-128並無原碼和反碼錶示.(對-128的補碼錶示[1000 0000]補算出來的原碼是[0000 0000]原, 這是不正確的)
使用補碼, 不只僅修復了0的符號以及存在兩個編碼的問題, 並且還可以多表示一個最低數. 這就是爲何8位二進制, 使用原碼或反碼錶示的範圍爲[-127, +127], 而使用補碼錶示的範圍爲[-128, 127].
由於機器使用補碼, 因此對於編程中經常使用到的32位int類型, 能夠表示範圍是: [-231, 231-1] 由於第一位表示的是符號位.而使用補碼錶示時又能夠多保存一個最小值.
計算機巧妙地把符號位參與運算, 而且將減法變成了加法, 背後蘊含了怎樣的數學原理呢?
將鐘錶想象成是一個1位的12進制數. 若是當前時間是6點, 我但願將時間設置成4點, 須要怎麼作呢?咱們能夠:
1. 往回撥2個小時: 6 - 2 = 4
2. 往前撥10個小時: (6 + 10) mod 12 = 4
3. 往前撥10+12=22個小時: (6+22) mod 12 =4
2,3方法中的mod是指取模操做, 16 mod 12 =4 即用16除以12後的餘數是4.
因此鐘錶往回撥(減法)的結果能夠用往前撥(加法)替代!
如今的焦點就落在瞭如何用一個正數, 來替代一個負數. 上面的例子咱們能感受出來一些端倪, 發現一些規律. 可是數學是嚴謹的. 不能靠感受.
首先介紹一個數學中相關的概念: 同餘
兩個整數a,b,若它們除以整數m所得的餘數相等,則稱a,b對於模m同餘
記做 a ≡ b (mod m)
讀做 a 與 b 關於模 m 同餘。
舉例說明:
4 mod 12 = 4
16 mod 12 = 4
28 mod 12 = 4
因此4, 16, 28關於模 12 同餘.
正數進行mod運算是很簡單的. 可是負數呢?
下面是關於mod運算的數學定義:
上面是截圖, "取下界"符號找不到如何輸入(word中粘貼過來後亂碼). 下面是使用"L"和"J"替換上圖的"取下界"符號:
x mod y = x - y L x / y J
上面公式的意思是:
x mod y等於 x 減去 y 乘上 x與y的商的下界.
以 -3 mod 2 舉例:
-3 mod 2
= -3 - 2xL -3/2 J
= -3 - 2xL-1.5J
= -3 - 2x(-2)
= -3 + 4 = 1
因此:
(-2) mod 12 = 12-2=10
(-4) mod 12 = 12-4 = 8
(-5) mod 12 = 12 - 5 = 7
再回到時鐘的問題上:
回撥2小時 = 前撥10小時
回撥4小時 = 前撥8小時
回撥5小時= 前撥7小時
注意, 這裏發現的規律!
結合上面學到的同餘的概念.實際上:
(-2) mod 12 = 10
10 mod 12 = 10
-2與10是同餘的.
(-4) mod 12 = 8
8 mod 12 = 8
-4與8是同餘的.
距離成功愈來愈近了. 要實現用正數替代負數, 只須要運用同餘數的兩個定理:
反身性:
a ≡ a (mod m)
這個定理是很顯而易見的.
線性運算定理:
若是a ≡ b (mod m),c ≡ d (mod m) 那麼:
(1)a ± c ≡ b ± d (mod m)
(2)a * c ≡ b * d (mod m)
若是想看這個定理的證實, 請看:http://baike.baidu.com/view/79282.htm
因此:
7 ≡ 7 (mod 12)
(-2) ≡ 10 (mod 12)
7 -2 ≡ 7 + 10 (mod 12)
如今咱們爲一個負數, 找到了它的正數同餘數. 可是並非7-2 = 7+10, 而是 7 -2 ≡ 7 + 10 (mod 12) , 即計算結果的餘數相等.
接下來回到二進制的問題上, 看一下: 2-1=1的問題.
2-1=2+(-1) = [0000 0010]原 + [1000 0001]原= [0000 0010]反 + [1111 1110]反
先到這一步, -1的反碼錶示是1111 1110. 若是這裏將[1111 1110]認爲是原碼, 則[1111 1110]原 = -126, 這裏將符號位除去, 即認爲是126.
發現有以下規律:
(-1) mod 127 = 126
126 mod 127 = 126
即:
(-1) ≡ 126 (mod 127)
2-1 ≡ 2+126 (mod 127)
2-1 與 2+126的餘數結果是相同的! 而這個餘數, 正式咱們的指望的計算結果: 2-1=1
因此說一個數的反碼, 其實是這個數對於一個膜的同餘數. 而這個膜並非咱們的二進制, 而是所能表示的最大值! 這就和鐘錶同樣, 轉了一圈後總能找到在可表示範圍內的一個正確的數值!
而2+126很顯然至關於鐘錶轉過了一輪, 而由於符號位是參與計算的, 正好和溢出的最高位造成正確的運算結果.
既然反碼能夠將減法變成加法, 那麼如今計算機使用的補碼呢? 爲何在反碼的基礎上加1, 還能獲得正確的結果?
2-1=2+(-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]補 + [1111 1111]補
若是把[1111 1111]當成原碼, 去除符號位, 則:
[0111 1111]原 = 127
其實, 在反碼的基礎上+1, 只是至關於增長了膜的值:
(-1) mod 128 = 127
127 mod 128 = 127
2-1 ≡ 2+127 (mod 128)
此時, 錶盤至關於每128個刻度轉一輪. 因此用補碼錶示的運算結果最小值和最大值應該是[-128, 128].
可是因爲0的特殊狀況, 沒有辦法表示128, 因此補碼的取值範圍是[-128, 127]
本人一直不善於數學, 因此若是文中有不對的地方請你們多多包含, 多多指點!
轉自:http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html