最近在讀《深刻理解計算機系統》(CSAPP),第二章中關於補碼的描述頗有意思,在書中並無詳細敘述補碼的由來和爲何要使用補碼來表示有符號數,而不是用原碼和反碼。相反這本書詳細的敘述了補碼的數學表示,以及公式的推導!對補碼的由來卻一筆帶過,甚至原碼和反碼只是簡單的在後面的籃框提示中提了一下,根本沒有出如今正文。性能
這在必定程度上形成了個人閱讀困難,因而在搜索引擎的幫助下,我查了不少資料,瞭解到補碼的更多細節,以及這個神奇的編碼方式如何幫助人們設計CPU時節省大量的精力和金錢。這裏我把他記錄下來。搜索引擎
計算機中的信息都是用二進制表示的,在表示無符號數時並無什麼問題,可是在表示有符號整數時就出現了問題,如何在二進制中區分正數與負數呢?
區分正數和負數的另外一個意義在於若是可以準確的表示出、負數,咱們就能夠化減法爲加法,如5-4就能夠表示爲5+(-4),至於爲何那麼多先輩要執着於把減法化爲加法,緣由很簡單,對計算機的電路而言,計算加法要比計算減法方便的多。
不少人提出了有符號整數的編碼方式,其中有:原碼(Sign-Magnitude)、反碼(ones' Complement)、補碼(two's Complement)。編碼
原碼的英文爲sign-magnitude,第一個單詞爲符號的意思,第二個單詞通過查詢有「大小」的意思,大意應該爲符號-數值的組合,在原碼中,第一個數字表明符號位,1表明負數,0表明正數,餘下的爲數值位,用來表示具體的數值。這種編碼方式最簡潔易懂,也最符合人的思惟。好比:-5用4位二進制原碼來表示就是1101。
CSAPP中給出的原碼數學公式爲:
由此咱們能推斷出SMAXw=2w-1-1,SMINw=-(2w-1-1)。如八位二進制原碼的範圍應該爲[-127,127]。
使用原碼來讓機器作運算彷佛是可行的,可是實際上,這種方法對機器來講倒是十分困難的,機器沒法像人同樣判斷出0是+,1是-,設計出一個能讓機器判斷正負的電路也是十分複雜和困難的。
實際上目前咱們不多用原碼來作運算,原碼目前經常用來做爲咱們求補碼的一個過渡。翻譯
反碼的英文爲ones'complement,乍看之下有些迷惑,翻譯過來是「不少個1的補」,彷佛更迷惑了。。。
其實反碼確實能夠用「不少1的補」來理解,咱們都知道w位二進制數表示的最大範圍是2w-1,用位向量來表示就是[111111....11111],就是不少個1。假設x是正數,求-x的補碼的含義實際上就是求[11111...111]-x,也就是(2w-1-x)(其中w爲位數),這是反碼的一種計算方式,另外一種計算方式就是用原碼計算,原碼的符號位不變,剩下的數值位所有取反,好比1011的反碼就位1100。這和上面的計算方法獲得的結果是一致的。
固然要注意的是上面所說的計算方法只對負整數有做用,正整數的反碼實際上和正常的無符號數編碼沒有區別。
反碼的另外一種具體數學解釋是在CSAPP中看到的:
由此咱們能推斷出OMAXw=2w-1-1,OMINw=-(2w-1-1)。如八位二進制原碼的範圍應該爲[-127,127]。
這個公式應該能夠經過上面的提到的計算方法嚴格證實出來。使用此公式能夠快速的算出反碼錶示的值。
反碼的優勢是計算快捷,不只僅是對人而言,計算一個負數的補碼,只須要將其的正數表示按位取反便可,對於機器來講,取反可比作加減法快多了。
反碼實際上能夠用來作二進制運算,它的規則是從低位到高位逐列進行計算。1和1相加是0但要產生一個進位1,0和1相加是1,0和0相加是0。若最高位相加後產生進位,則最後獲得的結果要加1。(反碼運算的原理將在下面的補碼運算中解釋)。
須要注意的是與原碼不一樣的是,反碼的運算符號位與數值一塊兒參加運算。
用反碼運算,其運算結果亦爲反碼。在轉換爲真值時,若符號位爲0,數位不變;若符號位爲1,應將結果求反纔是其真值。設計
原碼和反碼在不考慮性能的狀況下確實可以用來作二進制有符號整數的計算,歷史上也確有基於這兩種編碼方式的機器。可是這兩種編碼卻存在不少難以解決的缺陷。
考慮四位二進制數[0000],在原碼中表示0,在反碼中也表示0,而對於原碼來講[1000]也表示0,對於反碼來講[1111]也表示0。通常咱們把[0000]稱爲+0,而另外一種表示方法稱爲-0。
一個數字有兩種編碼方式,這顯然是不合理的!
爲了解決這種不合理咱們引入了補碼的概念,它繼承了反碼易於計算的優勢,也解決了原碼難以計算的問題。指針
介紹完了原碼反碼,在介紹補碼以前,爲了便於理解,咱們首先要介紹一下模的概念:blog
「模」是指一個計量系統的計數範圍。如時鐘等。計算機也能夠當作一個計量機器,它也有一個計量範圍,即都存在一個「模」。
例如:
時鐘的計量範圍是1~12,模=12。表示n位的計算機計量範圍是0~2^(n) -1,模=2^(n)。
「模」實質上是計量器產生「溢出」的量,它的值在計量器上表示不出來,計量器上只能表示出模的餘數。任何有模的計量器,都可化減法爲加法運算。
在計算機中,計算加法要比計算減法要容易和便宜,那麼到底如何在計量裝置中用加法代替減法呢?
這裏咱們能夠用時鐘舉一個例子:
這是一個只有時針的時鐘,目前它指向9。若是咱們想讓他回退到4。咱們有什麼辦法呢?
最簡單的方法是,倒退五個單位,9-5=4。可是這裏咱們不想讓指針回退,是否有另外的方法可以讓指針指向4呢?
答案是把指針向前撥7個單位。
爲何可以這麼作呢,這就是前面咱們提到的「模」的做用。
9+7=16=12+4,在時鐘這個計量機器上進位並不能顯示出來,因此最後時鐘上顯示的是4。
在以12模的系統中,加7和減5效果是同樣的,所以凡是減5運算,均可以用加7來代替。對「模」而言,8和4互爲補數。實際上以12模的系統中,11和1,10和2,9和3,8和4,6和6都有這個特性。共同的特色是二者相加等於模。
一樣的概念在計算機中也徹底成立,考慮一個四位的二進制數,它的範圍爲[0,255],模爲256,把這個概念擴展一下。w位的二進制數的模就爲2w。
模就是補碼之因此可以進行二進制計算的基本原理。繼承
如今咱們來談談本文的主角補碼(Two's complement),它的英文名依舊很迷惑,不過不要緊,下面我會用引入模的概念來幫助咱們理解補碼的具體含義。
咱們知道一個模爲10的計算系統中,-4與+6的結果是相同的(9+6=10+5=15,捨去進位最後的結果爲5與9-4相同),咱們把這個概念擴展:
在一個模爲M的系統中,咱們須要計算m-n,而-n在模爲M的系統中能夠轉化爲+(M-n),因此只須要計算m+(M-n),這樣就成功的把減法轉換成了加法。
把其擴展到二進制中:
在一個位數爲w的計算系統中,咱們須要計算m-n(n爲正整數),而-n在系統中能夠轉化爲(2w-n),因此只須要計算m+(2w-n)並把溢出的位捨去,而-n的補碼計算方法就是爲2w-n。這也是英文名爲Two's complement的緣由,意爲2的補,這裏只有一個2,因此用Two's complement。
補碼彷佛已經十分完美了,它不只解決了0的編碼重複問題(在補碼中0只有[0000]一種表示),也成功的把減法變成了加法,可是問題來了,爲了把減法變成加法而引入了減法不是像一個笑話嗎(手動狗頭)。
這個時候咱們就須要用到原碼和反碼了,咱們來仔細觀察一下反碼的計算方法,對一個負整數位數爲w的二進制數-n來講,它的反碼錶示爲(2w-1-n),對比補碼的表示2w-n,咱們可以發現補碼=反碼+1。
咱們很快就能推出對於一個負整數來講,咱們能夠算出它的二進制相反數表示,而後對全部的位取反,最後加一,獲得的就是補碼的值。這樣咱們就使用反碼成功解決了減法的問題。
固然了,和反碼同樣,上面的表示都是對負整數而言,正整數的無符號碼、原碼、反碼、補碼都是同樣的~
CSAPP給出了補碼的數學公式:
一樣的這個公式咱們也能夠經過證實獲得,而且經過這個公式咱們也能夠直接計算出補碼的實際值。
由此咱們能推斷出由此咱們能推斷出TMAXw=2w-1-1,TMINw=-2w-1。如八位二進制原碼的範圍應該爲[-128,127]。這也是爲何在通常的程序設計語言中,無符號數據類型的負數範圍要比正數大了。
補碼的計算就十分簡單了,直接相加就能夠獲得答案了~(固然溢出進位依然是要捨去的。),這也是反碼的計算原理。索引
補碼的出現解決了不少問題,出於崇敬我上網搜索了補碼的發明者,結果是。。。。沒有搜索到,而且通過查證,早在1645年,Pascal的計算器用的十進制補碼。後來到了20世紀,經由馮諾依曼引入到了計算機中。
經典的理論與知識老是在流傳中越發讓人感到先輩們的偉大,想必3個世紀以前的那位大師在使用補碼來解決計算問題時並無想到有一天信息時代的崛起會讓他的發現即便到了今天也依舊熠熠生輝。數學