1. 程式人生 > >原碼,反碼,補碼雜談

原碼,反碼,補碼雜談

http 同余 而已 機器 wan 機器數 整理 把他 需要

本文從原碼講起。通過簡述原碼,反碼和補碼存在的作用,加深對補碼的認識。力爭讓你對補碼的概念不再局限於:負數的補碼等於反碼加一

接觸過計算機或電子信息相關課程的同學,應該都或多或少看過補碼這哥仨。每次都是在課本的最前幾頁,來上這麽一段:什麽反碼是原碼除符號位,按位取反。補碼等於反碼加一。然後給整得莫名其妙,稀裏糊塗地,接著就是翻頁,反正後面的內容也跟三碼沒多大關系。

我原來也是看了好幾遍都沒看懂。古人雲:事不過三。學C語言的時候,看過一次。不懂?看《計算機基本組成原理》的時候看過,還是不懂!到了大三,上《單片微機原理與接口技術》的時候仍舊是不懂。到了期末,復習的時候,和宿舍的人瞎聊。說講講這些碼呀,我說我也不是很清楚呀。然後就一邊說怎麽求碼,一邊算。玩著玩著,突然就明白了。我說好,打住。不說了,放假我在好好整理下思路,於是就有了這篇額。。算討論帖吧。

好了,廢話不多說。開始我們的原碼,反碼,補碼之旅。

(一)預備知識

認識二進制,十六進制。會二進制與十進制的相互轉化運算

由計算機的硬件決定,任何存儲於計算機中的數據,其本質都是以二進制碼存儲。

根據馮~諾依曼提出的經典計算機體系結構框架。一臺計算機由運算器,控制器,存儲器,輸入和輸出設備組成。其中運算器,只有加法運算器,沒有減法運算器(據說一開始是有的,後來由於減法器硬件開銷太大,被廢了 )

所以,計算機中的沒法直接做減法的,它的減法是通過加法來實現的。你也許會說,現實世界中所有的減法也可以當成加法的,減去一個數,可以看作加上這個數的相反數。當然沒錯,但是前提是要先有負數的概念。這就為什麽不得不引入一個該死的符號位。

而且從硬件的角度上看,只有正數加負數才算減法。
正數與正數相加,負數與負數相加,其實都可以通過加法器直接相加。

原碼,反碼,補碼的產生過程,就是為了解決,計算機做減法和引入符號位(正號和負號)的問題。

本文可能比較長,沒必要一下子讀完。原碼,反碼,補碼,按章讀。
重點在於講補碼,到了補碼可能有些繞,建議帶著筆,寫出二進制數一起算。

表達可能不夠清楚嚴謹,望見諒。
(二)原碼

原碼:是最簡單的機器數表示法。用最高位表示符號位,‘1’表示負號,‘0’表示正號。其他位存放該數的二進制的絕對值。

若以帶符號位的四位二進值數為例

 1010   最高位為‘1’,表示這是一個負數,其他三位為‘010’,
      即(0*2^2)+(1*2^1)+(0*2^0)=2(‘^’表示冪運算符)
      所以1010表示十進制數(-2)。

下圖給出部份正負數數的二進制原碼表示法
技術分享
OK,原碼表示法很簡單有沒有,雖然出現了+0和-0,但是直觀易懂。
於是,我們高興的開始運算。

0001+0010=0011    1+2=3OK
0000+1000=1000    (+0+(-0)=-0 額,問題不大
0001+1001=1010    1+(-1)=-2

噢,1+(-1)=-2,這仿佛是在逗我呢。

於是我們可以看到其實正數之間的加法通常是不會出錯的,因為它就是一個很簡單的二進制加法。

而正數與負數相加,或負數與負數相加,就要引起莫名其妙的結果,這都是該死的符號位引起的。0分為+0-0也是因他而起。

所以原碼,雖然直觀易懂,易於正值轉換。但用來實現加減法的話,運算規則總歸是太復雜。於是反碼來了。

(三)反碼

我們知道,原碼最大的問題就在於一個數加上他的相反數不等於零。

例如:0001+1001=1010 (1+(-1)=-2) 0010+1010=1100 (2+(-2)=-4)

於是反碼的設計思想就是沖著解決這一點,既然一個負數是一個正數的相反數,那我們幹脆用一個正數按位取反來表示負數試試。

反碼:正數的反碼還是等於原碼

負數的反碼就是他的原碼除符號位外,按位取反。

若以帶符號位的四位二進制數為例:

3是正數,反碼與原碼相同,則可以表示為0011
-3的原碼是1011,符號位保持不變,低三位(011)按位取反得(100
所以-3的反碼為1100

下圖給出部分正負數的二進制數反碼表示法
技術分享

對著上圖,我們再試著用反碼的方式解決一下原碼的問題

0001+1110=1111 (1+(-1)= - 0)

互為相反數相加等於0,解決。雖然是得到的結果是1111也就是-0

好,我們再試著做一下兩個負數相加

1110(-1)+1101(-2)=1011(-4)

噢,好像又出現了新問題

(-1)+(-2)=(-4)?

不過好像問題不大,因為1011(是-4的反碼,但是從原碼來看,他其實是-3。巧合嗎?)

我們再看個例子吧

1110(-1)+1100(-3)=1010(-5)

確實是巧合,看來相反數問題是解決了,但是卻讓兩個負數相加的出錯了。

但是實際上,兩個負數相加出錯其實問題不大。我們回頭想想我們的目的是什麽?是解決做減法的問題,把減法當成加法來算。

兩個正數相加和兩個負數相加,其實都是一個加法問題,只是有無符號位罷了。而正數+負數才是真正的減法問題。

也就是說只要正數+負數不會出錯,那麽就沒問題了。負數加負數出錯沒關系的,負數的本質就是正數加上一個符號位而已。

在原碼表示法中兩個負數相加,其實在不溢出的情況下結果就只有符號位出錯而已(1001+1010=0011)

反碼的負數相加出錯,其實問題不大。我們只需要加實現兩個負數加法時,將兩個負數反碼包括符號位全部按位取反相加,然後再給他的符號位強行置‘1’就可以了。

所以反碼表示法其實已經解決了減法的問題,他不僅不會像原碼那樣出現兩個相反數相加不為零的情況,而且對於任意的一個正數加負數,如:
0001(1)+1101(-2)=1110(-1) 計算結果是正確的。所以反碼與原碼比較,最大的優點,就在於解決了減法的問題。

但是我們還是不滿足為什麽 0001+1110=1111 (1+(-1)=-0) 為什麽是-0

而且雖然說兩個負數相加問題不大,但是問題不大,也是問題呀。好吧,處女座。接下來就介紹我們的大boss補碼

(四)補碼

補碼:正數的補碼等於他的原碼
負數的補碼等於反碼+1。
(這只是一種算補碼的方式,多數書對於補碼就是這句話)

在《計算機組成原理中》,補碼的另外一種算法 是

負數的補碼等於他的原碼自低位向高位,尾數的第一個‘1’及其右邊的‘0’保持不變,左邊的各位按位取反,符號位不變。

OK,補碼就講完了。再見!!

還是莫名其妙有沒有,為什麽補碼等於反碼加1,為什麽自低位向高位取反……………….?

其實上面那兩段話,都只是補碼的求法,而不是補碼的定義。很多人以為求補碼就要先求反碼,其實並不是。

那些雞賊的計算機學家,並不會心血來潮的把反碼+1就定義為補碼。只不過是補碼正好就等於反碼加1罷了。

所以,忘記那些書上那句負數的補碼等於它的反碼+1。就這句話把我們帶入了理解的誤區。

這就是後來我明白為什麽我看的那本《計算機組成原理》,要特意先講補碼,再講反碼。

然後說負數的補碼等於他的原碼自低位向高位,尾數的第一個‘1’及其右邊的‘0’保持不變,左邊的各位按位取反,符號位不變。

但是上面這句話,同樣不是補碼的定義,它只是補碼的另外一種求法。它的存在,告訴我們忘記那句該死的‘反碼+1’它並不是必須的。

如果你有興趣了解,補碼的嚴格說法,我建議你可以看一下《計算機組成原理》。它會用‘模’和‘同余’的概念,嚴謹地解釋補碼。

接下來我只想聊聊補碼的思想。

(五)補碼的思想

補碼的思想,第一次見可能會覺得很繞,但是如果你肯停下來仔細想想,絕對會覺得非常美妙。

補碼的思想其實就來自於生活,只是我們沒註意到而已。時鐘,經緯度,《易經》裏的八卦。

補碼的思想其實就類似於生活中的時鐘

好吧,我其實不想用類似,好像這種詞,因為類比的,終究不是事物本身。而且不嚴謹會讓我懷疑我不是工科僧,說得好像我嚴謹過似的,哈哈

如果說現在時針現在停在10點鐘,那麽什麽時候時針會停在八點鐘呢?

簡單,過去隔兩個小時的時候,是八點鐘。未來過十個小時的時候也是八點鐘

也就是說時間正撥10小時,或是倒撥2小時都是八點鐘。

也就是10-2=8,而且 10+10=8(10+10=10+2+8=12+8=8)

這個時候滿12說明時針在走第二圈了,又走了8小時,所以時針正好又停在八點鐘。

所以12在時鐘運算中,稱之為模,超過了12就會重新從1開始算了。

也就是說, 10-2和10+10從另一個角度來看是等效的,它都使時針指向了八點鐘。

既然是等效的,那在時鐘運算中,減去一個數,其實就相當於加上另外一個數(這個數與減數相加正好等於12,也稱為同余數)

這就是補碼所謂模運算思想的生活例子

在這裏,我們再次強調原碼,反碼,補碼的引入是為了解決做減法的問題。在原碼,反碼表示法中,我們把減法化為加法的思維是減去一個數,等於加上一個數的相反數,結果發現引入了符號位,卻因為符號位造成了各種意向不到的問題。

但是從上面的例子中,我們可以看到其實減去一個數,對於數值有限制,有溢出的運算(模運算)來說,其實也相當於加上這個數的同余數。

也就是說,我們不引入負數的概念,就可以把減法當成加法來算。所以接下來我們聊4位二進制數的運算,也不必急於引入符號位。因為補碼的思想,把減法當成加法時並不是必須要引入符號位的

而且我們可以通過下面的例子,也許能回答另一個問題,為什麽負數的符號位是‘1’,而不是正數的符號位是‘1’。

(六)補碼實例

好吧,接下來我們就做一做四位二進制數的減法吧(先不引入符號位)

0110(6)-0010(2)【6-2=4,但是由於計算機中沒有減法器,我們沒法算】

這個時候,我們想想時鐘運算中,減去一個數,是可以等同於加上另外一個正數(同余數)

那麽這個數是什麽呢?從時鐘運算中我們可以看出這個數與減數相加正好等於模。

那麽四位二進制數的模是多少呢?也就是說四位二進制數最大容量是多少?其實就是2^4=16=10000B

那麽2的同余數,就等於10000-0010=1110(14)

既然如此

0110(6)-0010(2)=0110(6)+1110(14)=10100(20=16+4)

OK,我們看到按照這種算法得出的結果是10100,但是對於四位二進制數,最大只能存放4位(硬件決定了),如果我們低四位,正好是0100(4),正好是我們想要的結果,至於最高位的‘1’,計算機會把他放入psw寄存器進位位中。8位機則會放在cy中,x86會放在cf中(這個我們不作討論)

這個時候,我們再想想在四位二進制數中,減去2,就相當於加上它的同余數14(至於它們為什麽同余,還是建議看《計算機組成原理》)

但是減去2,從另外一個角度來說,也是加上(-2)。即加上(-2)和加上14其實得到的二進制結果除了進位位,結果是一樣的。

如果我們把1110(14)的最高位看作符號位後就是(-2)的補碼,這可能也是為什麽負數的符號位是‘1’而不是‘0’

而且在有符號位的四位二進制數中,能表示的只有‘-8~7’,而無符號位數(14)的作用和有符號數(-2)的作用效果其實是一樣的。

那正數的補碼呢?加上一個正數,加法器就直接可以實現。所以它的補碼就還是它本身。

下圖給出帶符號位四位二進制的補碼表示法
技術分享

到這裏,我們發現原碼,反碼的問題,補碼基本解決了。

在補碼中也不存在負零了,因為1000表示-8

這是因為根據上面的補碼圖,做減法時,0001(1)+1111(-1)=0000
我們再也不需要一個1000來表示負0了,就把它規定為-8

負數與負數相加的問題也解決了1111(-1)+1110(-2)=1101(-3)

可能說得有點繞,但是實在是沒辦法。其實我覺得補碼還可以這樣畫。
技術分享

很優美有沒有,如果你想想地理課本,0不就相當於本初子午線,-8不就是180°,而正數相當於西經,負數相當於東經。

(七)為何這樣求補碼

然後我們再來看看為什麽負數的補碼的求法為什麽是反碼+1

因為負數的反碼加上這個負數的絕對值正好等於1111,再加1,就是1000,也就是四位二進數的模

而負數的補碼是它的絕對值的同余數,可以通過模減去負數的絕對值,得到他的補碼。

所以 負數的補碼就是它的補碼+1。

有點繞吧,只能說很難算清楚,你們還是自己算算吧。還有上面我提到的另外一種算法。

接下來,我要說一下我自己算補碼的小技巧。

看上面那個圖。

如果我們把-8當成負數的原點。那麽-5的補碼是多少呢?

-5=-8+3

-5的補碼就是-8的補碼加3

1000(-8 +00113)=1011(-5)

所以完全可以口算出-5的補碼是1011

當然,也可以記住-1的補碼是1111口算減法得出

對於八位加法器的話,可以把-128當補碼原點。十六位可以把-32768當補碼原點。

是的,128256(八位二進制數的模)的一半,3276865536(十六位二進數的模)的一半

也很方便有沒有,而且簡單的是

補碼原點總是最高位是‘1’,其他位是‘0’

所以做加法總是簡單得可以口算。

OK,原碼,反碼,補碼之旅就到這裏結束。補碼第一次看總會覺得很繞,想言簡意賅,就怕哪裏遺漏了。講得細致,又不免連自己都覺得啰裏啰嗦。謝觀!

原碼,反碼,補碼雜談