理解整數為什么存成補碼的正確姿勢!


背景問題:你知道計算機中以什么形式存儲整數嗎?是符號位加值位嗎?值位是按照正常的二進制方式存儲的嗎?假如用3位二進制進行存儲,符號位0正1負,1是存成001,-1是存成101嗎?
答:使用補碼的方式而不是正常的方式存儲,雖然是符號位加值位,但符號位承載的信息和值位的值不是你想象中的方式,比如用3位二進制進行存儲,符號位0正1負,1會存成001,-1會存成111 

首先來回憶一下剛學計算機時候的教材里是怎么解釋補碼的:

- 原碼表示法是機器數的一種簡單的表示法。其符號位用0表示正號,用1表示負號,數值一般用二進制形式表示。
- 機器數的反碼可由原碼得到。如果機器數是正數,則該機器數的反碼與原碼一樣;如果機器數是負數,則該機器數的反碼是對它的原碼(符號位除外)各位取反而得到的。
- 機器數的補碼可由原碼得到。如果機器數是正數,則該機器數的補碼與原碼一樣;如果機器數是負數,則該機器數的補碼是它的反碼在未位加1而得到的。
- 現代計算機中普遍使用補碼表示法,而不是原碼。

 WTF!WHY?!

OK,下面我們來一一解答

 

一、計算機為什么使用補碼的形式存儲整數

  出於簡化計算機基本電路的考慮,讓加減法都只需要用加法電路實現。所以需要把減去一個正數或加上一個負數都用加上一個正數的方式來表示,於是在存儲的時候,負數被直接存儲成一種可以直接當成正數來相加的形式 (正數不變,所以以后的討論中有時候略去正數),這種形式就是補碼

二、那么補碼具體是什么?補碼是怎么做到加一個數跟減另一個數一樣效果的?

1. 先從時鍾這個身邊的例子理解 “加一個數跟減另一個數一樣效果”
  假設你對鍾的時候如果發現它是6點,但實際上現在是2點,也就是它走快了4個小時,你可以有兩種做法進行校正,一種是逆時針撥回4個小時到2點,另一種是順時針撥6個小時到12點然后再撥2小時,也就是順時針撥8個小時。也就是對於時鍾的表盤來說,-4 = +8,同樣還會有 -1 = +11,-5 = +7,甚至還有 -4 = +8 = +20 = +32 = -16
  他們間隱藏了什么規律呢?在數學中,-4、+8、+20、+32、-16可以歸為符合某個條件的同一類數字——對於模12同余。wiki上對於模的定義是 “兩個整數a、b,若它們除以正整數m所得的余數相等,則稱a、b對於模m同余”
  而在一個可溢出計數系統中,把計數系統容量作為模,那么所有對此模同余的數在此計數系統中都會有同樣的表示(加這個數也一樣)。比如時鍾表盤就是一個可溢出計數系統,模為12;一個n位二進制構成的計數系統中,因為會舍棄溢出的高位,所以也是一個可溢出的計數系統,模為2^n(從0數到2^n-1)
  所以假設一個3位二進制構成的模為8的計數系統,-2,-10,6,14都表示同樣的數,也就是減10和加14是一樣的效果

2. 引出“補碼”
  為了讓“補碼”實現 “加一個正數跟加一個負數(減一個正數) 一樣的效果”,“補碼”就可以是跟原負數對於模同余的正數
  在計算機中為了減少不必要的運算,負數的“補碼”就取其中最小的正數,正數直接就是它自己(而且如果負得太多,補一個模都不是正數,那其實算是左邊越界溢出)
  可能是通過原碼求“補碼”就是一個補模運算,所以把它叫做“補碼”
  (但要注意,這里的“補碼”都被我打上了雙引號,因為這還不是計算機里真正的存儲的補碼形式,它應該叫補數,不過相信我,已經差不多了)

三、但這種“補碼”表示還有問題

  通過轉換成“補碼”,減一個數確實變成加一個數了,看似很不錯,但卻有一個明顯的問題,那就是數本身的符號丟失了
  我們只存儲了一個在加法運算中方便運算的負數(甚至在結果為負數時計算結果都會不正確),但它卻不是一個能正常表示自己的負數(無法逆運算回去)
  比如3位二進制,正常能表示0~7,使用補碼法能進一步表示 -8~-1的運算,但不能真正表示 -8~-1

四、怎么完美解決“補碼”的正負表示問題?

  不知道大牛是怎么想到的,但最后我們看到的這種做法,是真的Magic。只需要在左邊加一個二進制位來表示正負,就能同時實現這幾個效果:

  1. 在保持補碼特性的前提下。也就是減一個數還是照樣變成加一個數
  2. 增加正負的表示。能真正表示 -8~-1了,就只用看符號位是0還是1
  3. 還能讓運算時不用另外區分符號位,直接把符號位當成值位進行運算,而結果的正負號自然會符合這個正負表示法(也就是符號位的進位和值位的進位都會自然地合理)(有一種理解方式是,把這個負號1當成減一個模)

  具體來說是在左邊加一個符號位,這個符號位不參與“補碼”的運算,始終表示數的正負,但在加法運算中跟值位一樣參與運算。加了一個這樣的符號位的“補碼”就是真正計算機中存儲的補碼了

五、最后總結正常人怎么求補碼

  對負數求最小正同余數(模為二進制值位存儲容量),把它們放入值位,符號位置1

  到這里“負整數為什么存成補碼?”這個問題基本就解答清楚了,你會發現里邊都沒有反碼的影子,對,就是這樣,用反碼以及那套教材里的計算補碼的方法來理解負整數的表示都是緣木求魚,原理上根本不需要它們,那它們是用來干什么的呢?值位取反加一這種算法是怎么冒出來的?接着往下看補充內容你就知道了

補充計算機中求補碼的簡便算法

  對於計算機來說,上面這個過程有點繁瑣,尤其是需要先求模,然后求最小正同余數,而且這個過程是非常基礎的過程,一點點優化都能有很大的性能提升
  然后這個過程就被大牛優化成這樣:先直接把負數的絕對值存到值位,符號位為負,然后對值位取反+1,就得到了補碼(這也是很多教材里告訴我們補碼怎么求的方法。。。用這個理解補碼,真是夠了)
  能看到其實優化的是求“補碼”:值位取反加一 = 最小正同余數,下面可以證明一下:

  1. 用3位二進制值位[abc]表示一個不會造成溢出的負數F:F = -( a*2^2 + b*2^1 + c)(a,b,c ∈ {0,1})
  2. 對F的值位取反 :F(反) = (1-a)*2^2 + (1-b)*2^1 + 1-c = 2^2 + 2^1 +1 - ( a*2^2 + b*2^1 + c ) = 2^3 -1 + F
  3. 然后3位二進制的模等於2^3,所以F的補碼 F(補) = F + 2^3
  4. 所以結果就出來了: F(反) = 2^3 -1 + F = F(補) -1 得到 F(補) = F(反)+1

可能是目前為止最好的理解補碼的文章XD,還請博友批評指正~


免責聲明!

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



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