內容來自:https://blog.csdn.net/zhuozuozhi/article/details/80896838
二進制負數的在計算機中采用補碼的方式表示。很多人很好奇為什么使用補碼,直接使用原碼表示多好,看上去更加直觀和易於計算。然而事實告訴我們,這種直觀只是我們人類的一廂情願罷了,在計算機看來,補碼才是它們最想要的。那么,為什么計算機使用補碼更好,又是如何通過補碼來計算數值的呢?
我看過網絡上很多解釋補碼的文章,幾乎一致的回答就是符號位不變,其他各位逐位求反再加一。在此我想說,這些都不是根本原理。誰都知道這么求,數電第一章就明確寫了怎么求,關鍵是為什么這么算,其中的原理是什么?
本文主要的內容就是深入講解補碼的原理,其中內容有相互引用成分及計算機基礎要求,不適合初學者閱讀。當然,隨便看看無所謂啦。
1.什么是補碼
這個沒有找到官方定義,只進行個人定義。
個人定義:補碼是計算機中用來表示負數,使得負數能夠使用加法器參與加法運算的一種碼。
加減是計算機中最常用的運算,加法一般使用加法器來實現,減法則使用減法器實現。那有什么辦法可以將減法變為加法,這樣就可以讓系統只實現加法即可,答案就是補碼。
理解補碼最簡單的例子就是時鍾。
例1:
假如一個時鍾現在顯示的是10點鍾,如何將它調到6點鍾?
解:有兩種方法,一是向后撥8個小時,二是向前撥4個小時
在這個例子中,8 和 4 互為補數,也就是說4的補碼是8,8的補碼是4,而這個時鍾的模就是12
注意:可能有人會想,在往后調8個小時雖然也調到了6點,但是他實際上比原來日期多了12小時。是的,的確如此,但是你的時鍾有地方存儲了這多余的12個小時嗎?答案是沒有,所以在你調完后,你沒有記錄這12個小時,換句話說,你把這溢出的12個小時自動舍棄了。當第二個人來查看鬧鍾時間的時候,他看到的時間就是准的。
例2:
一個數的數值是11,他的模是16,那么他的補碼是多少?
解:16-11 = 5,即補碼就是5。
2.模
關於模沒有找到固定的定義,簡單來說模就是一個循環的周期,在例1中,時鍾的一個周期就是12,所以模是12。一周的模是7天,一天的模是24小時。模是補碼的一個重要概念。
3.使用補碼運算
例3
16-13,模為32,使用補碼運算該算式。
解 (16 + (32-13))% 32 = 35 % 32 = 3
4.使用補碼進行二進制運算
有看過數電基礎的都應該記得,第一章就有說明如何求二進制補碼是如何運算的。正數的補碼即為自己,負數的補碼為符號位不變,其余逐位求反再加1。
使用該定義,先通過例子求出數值,再對例子進行詳細講解,為什么可以使用負數的補碼來運算。
例4 通過二進制求15-11的值
要想讓減法變加法,必須轉換算式為 15 + (-11)
15為正數,符號位為0,二進制表示為 01111
-11為負數,符號位為1,負數源碼為 11011 ,補碼符號位不變,其余逐位求反再 +1,即 10101
所以 15 + (-11) = 01111 + 10101 = 100100 舍棄最高位(與高地址位概念不同)溢出位,即00100,即+4
這是一個最簡單的補碼算法運算的例子,卻有很多不解之處。
1.為什么負數 (-11 )逐位求反再+1就可以代表原來的數?
2.為什么高位舍棄
3.為什么符號位能夠參與運算
先看問題1,-11 先不考慮符號位,觀察11的二進制表示,11使用二進制表示是 1011,將1011 逐位求反 得到 0100,因為是逐位求反,1011 +0100 = 1111 ,而1111 + 1 = 10000,發現了什么?10000是四位2二進制數的模。為什么10000是4位二進制數的模呢?原理也很簡單,4位寄存器最高能表示什么數?即0b1111 = 15 , 15 +1 =16 即溢出低四位歸 0,所有模為16。
所以不管幾位二進制數,取反后得到的值加原碼會剛好的到所有的位都是1的二進制數,再加一就剛好進位得到模。所以取反加一是無論如何都能取到補碼的。比如 :(原)101 + (補)((反)010 +1) = 8 ,(原)10 + (補)((反)01 +1)= 4。
所以,負數符號位不變,其余逐位求反 +1 只是算出補碼最簡單的方法,而不是理論基礎。
前面提到過,使用補碼代替原碼,計算后模掉溢出得到數值就可以得到計算的值了。
再看問題2,為什么高位舍棄,這個問題其實在例1中已經做了說明,因為你沒有存儲這個高位的空間,用最簡單的解釋來解釋就是,一個4位的寄存器,只能存儲數據的低四位,最高第五位沒地方放,就像例1中多出來的12小時,沒有地方存儲,那么就丟棄了,反正結果正確就行。可能有人會問,你說丟棄就丟棄嗎,丟了你怎么保證是正確的?
重新再看一次例4,現在考慮符號位,將符號位加入運算。
15 - 11 = 15 + (-11),其中 -11 為 11011(加了一位符號位,所以模為 16 * 2^1) 他的補碼為 10101 = 21
所以 15 -11 變為 15 +21 = 36 (01111 + 10101) ,想想看,原先是減一個數,變換后卻變成了一個比減數更大的加數了,能不溢出嗎。所以要模掉溢出位 36 % 32 = 4 。仔細觀察被模數36 = 1 00100 你會發現,在數據高2位以前的所有高位都是32的倍數,所以用32進行模運算就會全部清0,這就是為什么高位可以直接舍棄的原因,因為高位永遠會是模的倍數。
最后看問題3,為什么符號位能夠參與運算,可能在這前面一直被符號位困擾,為什么參與運算就沒有問題,看了這里的解釋,我相信你講不會再被困擾。
符號位對於我對二進制補碼的理解產生了很大的阻礙,我不明白為什么符號位可以參與運算,而且算出來還是對的。花了不少時間理解了符號位的含義。我想說,符號位不變,其余逐位求反 +1 這個定義很差。
首先放棄符號位就是計算機表示正數和負數用的這個思維,改成這樣理解,就會非常清晰:在有符號的數據類型中,第一位如果是0,那么這個數值是正數原碼,第一位如果是1,那么這個數值是負數補碼,這個數值是計算所得,而不是計算機設定的如此。
然后放棄負數補碼符號位不變,其余逐位求反再+1這個方法,使用如下負數補碼的求法。一個二進制數值如果是正數,那么將不變。如果是負數,那么這個二進制數最高位加一位0(如: 1010 變為 01010 ,位數變高一位,模需要乘2,即由16變32),對該數求補碼(01010 求補碼 10110) 。是不是發現,所謂的符號位不變,其余逐位求反再加一和 在數值前面再加一位0,再所有位求反再+1的效果是一樣的,只要求反,最高位0肯定會變成1。一旦知道第一位是0或者1,也就確定了他是正數還是負數了,采用這種理解法,運算符可以加入運算也就理所當然了。(被減數 + 負數補數)% 負數補數的模 = 帶符號計算值 這樣的計算方式能保證所有的符號位都能參與運算。而符號位不變,其余取反+1的解釋顯的符號位非常難以理解。
最后再看下重新思考這個運算思路,在運算中,如果遇到加法運算符,則直接運算符,如果遇到減法運算符,查看減數是正數還是負數,如果是負數,從補碼反求源碼,加入計算,如果遇到正數,則最高位加一位0,取反 +1 求補碼,加入運算。
到這里補碼的原理就差不多講清楚了,無非是使用模進行轉化,但是我們站在十進制的角度,去看二進制的東西,的確會顯得相當費解。
內容有相互嵌套成分,閱讀后回顧前面的內容可能會更加深入理解。
還有很多特殊情況沒有考慮,只講解了最核心的內容。
本文純屬個人理解,如有紕漏,請勿拍磚。
————————————————
版權聲明:本文為CSDN博主「zhuozuozhi」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zhuozuozhi/article/details/80896838