從模運算的角度看原碼和補碼
寫作的背景:之前在學習計算機基礎的過程當中,對於計算機原碼、反碼和補碼的相關知識一直處在一知半解的狀態,即僅僅只停留在會用的階段,但是對於計算機中引入補碼的原因,以及補碼是怎麼來的(從數學的角度看)類似這樣的問題自己一直處於懵逼狀態。雖然老師也曾經對此作出過解釋,但是自己一直本著會用就行的原則,所以也一直沒有學會。然而隨著課程的深入,自己漸漸的發現,對於這些計算機的底層知識的深刻理解還是十分重要的,所以自己又通過翻閱書籍以及查詢網上的相關資料試圖對原碼和補碼的問題能有一些深刻理解,終於經過不懈努力,總算有些收穫,特此記錄,以備不時之需。
1 引入補碼的原因
關於計算機中引入補碼的原因,我想大致分為兩個:
- 解決原碼的侷限性
- 將減法轉化為加法
1.1 原碼的侷限性
關於原碼的侷限性,我想對於學過計算機的朋友都知道(當然沒有今天這一遭,我是不知道的,腦子裡一團漿糊),對有符號數來說(為了方便下面都以8位2進位制數進行舉例),最高位作為符號位,其餘7位作為數值位,那麼對於零這樣一個比較特殊的值來說,對於它的原碼錶示是有兩種:\([0]=0000\ 0000\)和\([-0]=1000\ 0000\)兩種,眾所周知,出現這樣的情況是非常不好的,所以後面就引入了補碼成功彌補了原碼的侷限性。
1.2 將減法轉化為加法
學過計算機組成原理的都比較清楚的一點是,在計算機硬體裡是沒有做減法這麼一說的,在計算機裡所有的減法操作都會被轉化為加法操作,為了到達這個目的,所以引入了補碼,將兩個數的減法運算,直接轉換為對這兩個數補碼的加法運算(計算機中對於數的儲存也是存的是一個數的補碼),至於為什麼可以這樣做,在下面將會做出解答。
2 模運算的簡單介紹
對於模運算,想必大家都不陌生,模運算又被稱為時鐘運算。為什麼會被稱為時鐘運算呢?我們不妨用時鐘來舉例,假設現在是下午的三點,如果你想將時鐘調整到下午的一點,想一想你會有幾種辦法?
- 將時鐘逆時針撥動兩格
- 將時鐘順時鐘撥動十格
將上面的敘述轉換為數學語言就是,由3變到1,有兩種辦法,第一種,通過3-2的方法;第二種,通過3+10的方法。這時候你肯定會感到怪了,\(3 + 10 = 13\neq 1\) 啊,但是你別忘了我們現在可是在時鐘上舉例子,你現在可以回想一下12小時制和24小時制,13 和 1在時鐘上是不是指向了相同的位置?當然,說這個只是為了讓你直觀的感受一下,其實,通過觀察時鐘我們可以發現,在時鐘上一共就有12個刻度,分別是1~12(這裡僅僅只看時針),你任何一個大於12的整數通過你撥動時鐘(當然你要假定一個起點),都會唯一對應時鐘上的一個刻度。比如之前的那個例子,從3出發撥動10個格子,雖然從數學計算上是13,但是它在鐘錶上,就唯一對應了一個刻度1。(對於前面通過時鐘運算唯一對應這件事情有沒有讓你覺得熟悉,會不會讓你聯想到對映)其實,模運算說的也是這麼個東西。
對於模運算來說(比如:\(mod\ n\)),其實本質上就是就是將任何一個整數都對映到\([0,n-1]\)上,還是拿之前的那個例子來說,對於時鐘來說,n自然取12,那麼對於剛才那個3 + 10,我通過模運算\((3+10)\ mod\ 12\),就可以把3 + 10對映到時鐘能夠表示的一個具體刻度上去,最後的模運算的結果是1,這和我們之前通過撥動時針的出來的結果是一致的。現在你應該知道模運算為什麼又叫做時鐘運算了吧,其實模運算就是對時鐘現象的一種數學抽象,僅此而已。
3 模運算與原碼和補碼
從上面的那個例子我們不難看出,對於時鐘運算(即模運算)來說,\(3-2\)和\(3+10\)的結果是一樣的(即\((3-2)\ \equiv (3+10)\ mod\ 12\),同餘),前者是減法操作,後者是加法操作,這其實就給我們提供了一個將減法轉換為加法的思路。
對於8位二進位制數來說,能夠表示出來的範圍也就是0~255,對於大於255的數來說,我們可以通過模運算將它們都對映到0~255上來,這樣對於一個負數,比如:-2來說,其實-2是不在0~255這個範圍內的,於是我們就可以通過模運算,\(-2\ mod\ 256\)將它轉換為0~255上的數,即\(-2\ mod\ 256 = 254\),不難發現\(254\ mod\ 256 = 254\),也就是說\(-2\ \equiv 254\ mod\ 256\),也即是對於任意的一個\(a\),\(a-2\)可以轉換為\(a+254\),這樣我們就成功的將減法轉換為加法,對於任意的一個負數都可以通過上述的步驟進行轉換。
下面我們來說一說,原碼和補碼的事,那剛剛的那個例子我們來看看\(-2\)的原碼為1000 0010,\(-2\)的補碼為1111 1110,再看看254的補碼也為1111 1110,可以看到\(-2\)與254具有相同的補碼,也就是說\(-2\)的補碼其實就是\(-2\ mod\ 256\)的結果的二進位制表示,而我們都知道對於正數來說,原碼 = 補碼,所以到這我們可以發現,所謂的將兩個數的原碼運算轉換為補碼運算,其實本質上就是為了解決將減法運算轉換成減法罷了,而所謂的補碼,其實就是負數模運算之後的二進位制表示而已,只是後面為了敘述規範又統一給了個名字將負數模運算之後的二進位制表示叫做補碼。
再來看看之前對於原碼的侷限性的問題,在補碼中是否依舊存在,先來看看\([0]_補=0000\ 0000\),而\([-0]_補=0000\ 0000\)。可以發現,對於補碼來說,0就對應了一種編碼,原先的問題在補碼中也得到了較好的解決。
4 總結
補碼概念的引入其實主要就是為了解決運算過程中將減法運算轉換為加法運算的問題,而補碼的本質其實就是對負數進行模運算結果的二進位制表示,其實補碼主要也是針對負數來說的,對於正數來說原碼和補碼相同,補碼對於正數的意義也就沒負數那麼重要了。
5 參考資料
https://www.cnblogs.com/flowerslip/p/5933833.html
https://www.cnblogs.com/zhangziqiu/archive/2011/03/30/computercode.html