原碼、反碼、補碼的正(nao)確(can)打開方式


  我們知道日常生活中使用的數分為整數和實數,整數的小數點固定在數的最右邊,可以省略不寫,而實數的小數點則不固定。在計算機中只能識別和表示“0”和“1”,而無法識別小數點,因此要想使得計算機能夠處理日常使用的數據,小數點的問題是不可避免的。

  關於計算機系統中實數的表示,在下篇文章中會講解。本篇博客我們講解的是整數在計算機系統中如何表示。  

  在各種大學教材,各種網站論壇中,對於整數編碼表示方法的正確打開姿勢(姿勢要帥)如下:

  

1、機器數

  機器數(computer number)是數字在計算機中的二進制表示形式。機器數有2個特點:

  ①、符號數字化。因為計算機硬件只認識兩種物理狀態(用0和1表示),因此數據的正負號在機器里就用一位二進制0或者1來區分。在計算機用一個數的最高位存放符號, 0代表符號“+”,以1代表符號“-”。

  ②、機器數的大小受機器字長的限制。機器內部設備一次能表示的二進制位數叫機器的字長,一台機器的字長是固定的。字長8位叫一個字節(Byte),機器字長一般都是字節的整數倍,如字長8位、16位、32位、64位。

  比如在字長為8的計算機中,十進制數+5,其機器數為00000101;十進制數-5,其機器數為10000101。

2、真值

  計算機機器數真正的值稱為真值。因為機器數的最高位是符號位,所以我們在計算真值的時候要分區分開。

  比如上面講的機器數10000101,單純作為一個二進制數,我們轉換為十進制是133。但是其真值是不計算符號位的,其最高位的1表示"-"。所以10000101的真值為-5。

3、機器數的原碼、反碼、補碼三種形式

  前面我們講過機器數是在計算機中的二進制表示形式,但是在計算機中,這種表現形式又分為原碼、反碼、補碼等三種最常用的形式。

  ps:下面舉例都是字長為8。

  ①、原碼

原碼=符號位+真值

  比如:

  [+5]原碼=0 0000101

  [-5]原碼=1 0000101

  原碼表示與真值對應直觀,而且轉換也簡單。但是用原碼進行加減運算的時候,會出現以下問題:

使用原碼計算表達式:1 - 1 = 0
1 - 1 = 1 + (-1)= [00000001]原 + [10000001]原 = [10000010]原 = -2

  注意:計算機是沒有減法器,只有加法器,減法運算可以轉換為加上那個數的負數。

  我們發現通過原碼計算1 - 1 表達式結果居然是 -2。所以早期計算機機器數采用原碼編碼的時候,在進行原碼加減運算時,必須先判定是否是兩個異號數相加或兩個同號數相減,若是,則必須判定兩個數的絕對值大小,根據判斷結果決定運算結果符號,並用絕對值大的數減去絕對值小的數。也就是說用這樣一種形式進行加運算時,負數的符號位不能與其數值部分一道參加運算,而必須利用單獨的線路確定符號位。很顯然,這樣設計電路就很復雜,這是不經濟實用的,為了解決這個問題,反碼產生了。

  ②、反碼

反碼:正數的反碼與其原碼相同;負數的反碼是對其原碼逐位取反,但符號位除外。

  我們用反碼來計算 1 - 1

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

  看上去結果好像是正確的了,但是大家發現沒,結果是-0,雖然對於0的符號沒有什么實際意義。但是在計算機中,0如果用原碼和反碼表示會有兩種形式:

[+0]=[0000 0000]原=[0000 0000]反
[-0]=[1000 0000]原=[1111 1111]反

  兩種編碼就兩種編碼吧,只不過是多占用一個計算機表示數的編碼形式。只要結果是正確的,我們還是能夠忍受的,然而。。。。

  請用反碼計算表達式  2 -1 

2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]反 + [1111 1110]反 = [0000 0000]反 = [0000 0000]原 = +0

  是不是很奇怪,原碼計算 2 - 1 得到的結果居然是 0 。其實稍微分析計算過程我們也知道,再用反碼進行加法運算的時候發生了進位,而由於字長為8,進位就直接省略了,便造成了錯誤。這肯定是不被允許的,所以采用反碼的計算機解決辦法如下:

  反碼的符號位相加后,如果有進位出現,則要把它送回到最低位去相加(循環進位)。

2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]反 + [1111 1110]反+[0000 0001]循環進位 = [0000 0001]反 = [0000 0001]原 = +1
13 - 6 = 13 + (-6)= [0000 1101]原 + [1000 0110]原 = [0000 1101]反 +[1111 1001]反+[0000 0001]循環進位=[0000 0111]反 = [0000 0111]原 =+7

 

  采用反碼運算雖然較好的解決了原碼運算所遇到的困難或問題,但由於循環進位需要二次算術相加,延長了計算時間,這同樣給電路帶來麻煩。這時候補碼登場了。

  ③、補碼

補碼:正數的補碼與原碼相同,負數的補碼等於其反碼的末位加1

  我們來看下面這個例子:

  2 - 1 = 1

2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]反 + [1111 1110]反 = [0000 0010]補 + [1111 1111]補 = [0000 0001]補 = [0000 0001]原 =+1

  9 + 12 = 21

9 + 12 = [0000 1001]原 + [0000 1100]原 = [0000 1001]補 + [0000 1100]補 = [0001 0101]補 = [0001 0101]原 = 21 

  

  我們發現補碼運算就很簡單了,產生的進位直接舍去,而且不做多余的操作也解決了進位的問題。還有 +0 和 -0 的表示,在原碼和反碼都有兩種形式,但是補碼卻只有一種:

[+0]=[0000 0000]原=[0000 0000]反=[0000 0000]補
[-0]=[1000 0000]原=[1111 1111]反=[0000 0000]補

   

  就這樣我們完美的解決了計算機中整數運算的問題。計算機的機器數采用補碼的形式,我們在做算術運算的時候,既不需要額外的判斷,又能得到准確的結果。

  看上去本文應該結束了,然而......

  請求出 127+1 的值

4、溢出

  接着上面拋出的問題,127+1的值,我們現在程序中看看:

public static void main(String[] args) {
	byte x = 127;
	byte y = 1;
	byte k = (byte) (x+y);
	System.out.println(k);  //-128
	System.out.println(Byte.MIN_VALUE+"~"+Byte.MAX_VALUE); //-128~127
}

  byte在計算機正好是一個字節,也就是8位二進制序列。我們發現127+1結果不是128,反而是-128,這就是結果發生了溢出。因為byte表示數的范圍是-128-127,128超出了這個范圍。用補碼計算如下:

1 + 127 = [0000 0001]原 + [0111 1111]原 = [0000 0001]補 + [0111 1111]補 = [1000 0000]補

  我們發現這個數的符號位沒有發生進位,但是數值最高位發生了進位。在看前面的2-1

2 -1 = 2 + (-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]補 + [1111 1111]補 = [0000 0001]補 = [0000 0001]原 =+1

  這個表達式符號位和數值最高位發生了進位,但是結果卻是正確的。總結如下:

  只有一個高位進位或者符號位進位就為溢出的規則。

  溢出是每種編碼在運算時都不可避免的,一般來講結果超過字長所表示數的范圍都會發生溢出。而判斷機器是正常進位還是溢出的基本依據,在微型機中可用異或電路來實現上述的判斷。在實際編碼中解決辦法也很簡單,就是將結果用更大范圍的編碼形式接收即可。比如兩個byte類型的數相加,我們用 int 來接收即可。

public static void main(String[] args) {
	byte x = 127;
	byte y = 1;
	int k =  x+y;
	System.out.println(k);  //128
	System.out.println(Byte.MIN_VALUE+"~"+Byte.MAX_VALUE); //-128~127
}

  所以我們可以說用補碼進行運算,在不考慮溢出的情況下,結果都是正確的。確實也是這樣,但是......

  請求出 -128 的補碼?

5、劇情反轉

  上面的給出的問題,-128 的補碼,我們首先想到去求它的原碼,嗯,原碼應該是 [1000 0000],不對,第一位不是符號位嗎,[1000 0000]應該表示 -0。那應該怎么用原碼表示 -128呢,我們發現字長為 8 的計算機用原碼是無法表示的,反碼也是一樣。我們看看補碼,用 -127- 1 的表達式結果來計算 -128 的補碼:

(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]補 + [1000 0001]補 = [1000 0000]補

  -128的補碼形式為 [1000 0000],我們能通過算術表達式得到某個數的補碼形式,但是為什么直接就求不出來?那么計算機自己是怎么實現的呢?

  再來看這樣一個問題:我們日常使用的鍾表,比如現在鍾表指向的是 10點鍾,我要將鍾表調整到 6 點鍾,則有兩種撥法:

  ①、順時針將時針撥動 8 格

  ②、逆時針將時針撥動 4 (12-8) 格

  由此給大家普及一個概念叫 “模”,鍾表便是一個典型的模運算系統,其模數為12。

  同理,對於十進制兩位數,在將結果百位舍掉的情況下,50可以用60-10得到,或者60+90得到。這里的90也就是100-10得來的,那么我們就說十進制兩位數運算系統的模數為100。

  我們判定:兩個相加等於模的數互為補數。

  在模表示的范圍內做減法運算,可以將“X-Y”的減法變更為“X+Y的補數“的加法,當然這里不考慮結果溢出。

  上面我們舉的例子都是大數減小數,如果是小數減大數會怎樣?

  如果是10-80,結果應該是-70。但是如果按照 10+(100-80)的說法,結果是30。很明顯,30和-70不是同一個結果,而且也沒有產生百位進位。那我們應該怎么辦呢?

  解決辦法很簡單,就是讓這兩個數相等,而且這正好解決了負數的表示方法,-70的絕對值的補數正好是30。

  但是問題又來了,這里的30已經表示正數30了,現在又表示負數-70,那我們怎么知道它到底表示哪個數?

  為了解決這個問題,我們給這套規則規定一個范圍,原來是0~99的正數,現在既然要用部分正數來代替負數了,那就要規定一個范圍來使得一個數只代表一個含義,正好一人一半,0~49這個區間就代表正數,50~99的區間就用來代表各自補數的負值,例:98就代表-2

  所以0-99的編碼數可以表示的數的范圍為 -50-49。

 

  我們解決了十進制兩位數的減法運算,那么在字長為 8 的計算機系統中,我們又該如何呢?

  8位二進制數可以表示的數為2的8次方,0-255,一共 256 個數,0也要占用一位數。所以我們說 256 是8 位二進制數的模,這和上面說的十進制兩位數0-99,模為100是一樣的。

  我們按照前面講的邏輯,一半的數0-127,代表其正數本身,另一半的數128-255表示其補數的負值,即-1~-128。

  所以而 “X-Y”的減法 就用 “X+Y的補數” 的加法來表示,即將減法的形式轉換為加法的形式了,而且計算結果還是正確的。

  注意:這里還是一樣,不考慮結果的溢出,也就是計算值和結算結果都必須在-128~127之間,一旦超過這個范圍,結果就不准了,這也是程序員日常編碼說的int=int+int,如果結果大於int類型表示的范圍,那得出來的結果肯定不准。

  由此我們得出來的結論是:

  計算機編碼其實並沒有什么所謂的符號位,但是由於計算機沒有減法運算,為了將負數變為某個可以運算的編碼來進行加法運算,補碼產生了。這也間接說明了正數的補碼是不變的,而負數的解決辦法是首位不變,其余的取反再加1。

  我們上面說的補碼怎么得來的,就是 模-絕對值 。

  所以這個時候我說讓你求 -128的補碼,你馬上就會知

  256 - |-128|=128  而128的二進制補碼是不是就是 [1 0 0 0 0 0 0 0]

  讓你求 -1 的補碼,你馬上就會知

  256 - |-1| = 255,其255的二進制補碼形式就是[1111 1111]

  注意:關於這樣求補碼的具體數學證明,請參考《計算機組成與系統結構》。

  

 

6、總結

  本篇文章你可以直接從第 5 點開始看,忘掉計算機編碼的什么首位符號位,忘掉計算機補碼是由原碼取反加1,回歸簡單直白的理解。計算機是機器,硬件能理解的只有高低電平,也就是0或者1。它知道什么是符號位嗎?這些規則只不過是為了更好的完成減法運算所yy出來的。

  大學也學過這些編碼方式,但是都是背書式記憶,希望這篇文章能給大家帶來一些幫助。

  個人見解,如有錯誤歡迎大家拋磚!!!

  

參考書籍:《計算機組成與系統結構》

參考文章: https://www.zhihu.com/question/20458542?sort=created


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM