從計算機為什么用補碼存儲數據,衍生到存儲單元數據溢出

引言

先說幾句屁話,覺得啰嗦可以忽略跳過這段屁話。
俗話說:眼看他起高樓,眼看他宴賓客,眼看他樓塌了。我想這句話放在我們做技術的,也很合適——基礎不牢,地動山搖。
盡管我們很多人不是做基礎開發的,但是操作系統、數據結構和算法、計算機網絡、設計模式……這些IT領域的基礎性學科,對于我們來說其實挺重要的,比如做優化、跨語言學習、工程框架搭建……。基礎雖然不能決定眼前,但是能夠決定我們在這條路上走多遠。框架那么多、特效那么多,真心沒有興趣一個個都摸一遍,所以偶爾回過頭去翻看這些基礎性的東西是挺有意思的。

  • 是否還記得在學操作系統的時候,很困惑計算機為什么要用補碼存儲數據,而不是用我們人更容易理解的原碼來進行存儲呢?

關于這個問題,相信很多教這門課的老師以及工作多年的coder也解釋不清,甚至不知道這個概念。

  • 本文將嘗試從理性結合感性的角度去說明計算機為什么用補碼存儲數據,當我們明白這個問題后,那么,我們就可以去理解另一個衍生問題——數據溢出。我們先來看一段關于數據溢出的Java代碼片:
/*char是無符號數,16位存儲,表示范圍是0~2^16-1(即0~65535)*/
char ch = Character.MAX_VALUE; // ch為65535
ch += (char) 1; // 加1后,引起數據溢出,則ch為0

/*int是有符號數,32位存儲,表示范圍是-2^31~2^31-1(即-2147483648~2147483647)*/
int i = Integer.MAX_VALUE; // i為2147483647
i += 1; // 加1后,引起數據溢出,則i為-2147483648
  • 至于上述代碼片的執行結果為什么會這樣,暫時不解釋,希望通過文章循序漸進的過程來說明溢出的問題。

fuck概念

  • 計算機用二進制來表示數據,這個大家應該都了解(不了解的找塊板磚拍死自己算了)。
  • 沒有特殊說明,本文都以4位存儲單元來說明
  • 下面幾個小節會提到一些關鍵概念,不要對這些概念恐慌,這些概念會結合例子或者對比的形式,盡量以通俗簡潔的文字來說明,保證人人都能看的懂

加法器

  • 計算機只有加法器沒有減法器,兩個數的減法運算會被計算機轉換為加法運算。(先埋個伏筆——通過補碼進行表示,即可將減法運算轉換為加法運算)

模、補數

  • 在日常生活中,有許多化減為加點例子。我們以最平常的鐘表為例,時針逆時針撥x(0<x<12)格和時針順時針撥12-x格,效果是相同的。比如,時針從10點調整到5點有以下兩種方法:

    1. 時針逆時針撥5格,相當于做減法:10 -5 = 5
    2. 時針順時針撥7(即12 - 5)格,相當于做加法:10 + 7 = 12 +5 = 5(MOD=12)
  • 總結,x + (MOD - x) = MOD就是模,x和MOD - x就是一對“互補”的數,即原數x的補數為MOD - x或者原數MOD - x的補數為x。通過對鐘表撥時針的例子可以發現,用補數(7)代替原數(5),可把減法轉變為加法(出現的進位就是模,進位舍棄)。

二進制數的模,先來看下兩個個例子(此處我們忽略符號):

  1. 2位存儲所能表示的最大數是11(10進制:3 = 2^2 - 1),比他大1的是11 + 1 = 100(10進制:4 = 2^2),那么這個100則是2位存儲所能表示的所有數據的模。
  2. 4位存儲所能表示的最大數是1111(10進制:15 = 2^4 - 1),比他大1的數是1111 + 1 = 10000(10進制:16 = 2^4),那么這個10000則是4位存儲所能表示的所有數據的模。

通過對上面兩個例子可以推論:一個二進制數的最高位位數用n表示,那么該二進制數的模就是2^n

原碼、反碼、補碼

  • 先來看一張國內外教材對比的表(出自《計算機教育》2015年第10期的文章——《原碼、反碼和補碼的教學討論》)
國內外教材對比.png

注意下ones' complement和 two's complement的撇號(')的位置(學過英語的都知道,撇號(')在s前和s后的含義是不一樣的)

  • 給定一個有符號數x,來對比下國外和國內教材對原碼、反碼、補碼的表示:

    • 國外教材
      • sign and magnitude representation(原碼):最高位位符號位(0表示正數,1表示負數),剩余位(數據位)為x的大小。
      • ones' complement representation(反碼):如果x為正數,則是其二進制表示;如果x為負數,則是其對應正數的bit complement/bitwise NOT(按位取反)——執行每一位邏輯否定的一元操作。可用公式表示為:
        • [x]反 = (2^n - 1) - |X|(其中n為將符號位算在內的位數,|X|為絕對值)
      • two's complement representation(補碼):如果x為正數,則是其二進制表示;如果x為負數,則是其對應正數的二的補(所有位取反后加1)。可用公式表示為:
        • [x]補 = (2^n) - |X| = [x]反 + 1(其中n為將符號位算在內的位數,|X|為絕對值)
    • 國內教材
      • 原碼:最高位為符號位,剩余位(數據位)為x的絕對值。
      • 反碼:如果x為正數,則與原碼相同;如果x為負數,符號位保持不變,數據位取反。
      • 補碼:如果x為正數,則與原碼相同;如果x為負數,符號位保持不變,數據位取反,然后加1(若符號位有進位,則舍棄進位)。
  • 對比國內外教材的表述,是否發現高下立現:

    • 國內教材畫蛇添足,并且容易引起誤解:
      1. 原碼是反碼和補碼的基礎,反碼和補碼由原碼轉化而來
      2. 原碼、反碼和補碼的符號位相同
    • 國外教材,則非常通俗:
      1. 求解一個數的反碼和補碼,根本不需要知道原碼,直接通過它們的兩個對應公式即可,甚至可以說原碼與反碼和補碼沒有半毛錢關系,反倒是反碼和補碼存在關系——補碼 = 反碼 + 1
      2. 原碼的出發點是符號的表示(符號位),即用0表示正數,用1表示負數;反碼和補碼的出發點是減法的運算,即用兩個正數的加法取代兩個數的減法

狗日的國內教材和翻譯,真是誤人子弟啊

計算機為什么用補碼存儲數據

  • 上面鋪墊了這門久,終于要進入第一個正題——計算機為什么用補碼存儲數據。為了不引起混淆,我們就以國外教材對于原碼、反碼和補碼的表示法來進行說明。簡單起見,以4位存儲表示有符號數為例,通過原碼、反碼和補碼的表示法來生成一張表:

|有符號數(十進制)|sign and magnitude representation(原碼)|ones' complement representation(反碼),[x]反 = (2^n - 1) - \X|two's complement representation(補碼),[x]補 = (2^n) - \X|
|:-:|:-:|:-:|:-:|
|+7|0111|表示方式不變|表示方式不變|
|+6|0110|表示方式不變|表示方式不變|
|+5|0101|表示方式不變|表示方式不變|
|+4|0100|表示方式不變|表示方式不變|
|+3|0011|表示方式不變|表示方式不變|
|+2|0010|表示方式不變|表示方式不變|
|+1|0001|表示方式不變|表示方式不變|
|+0|0000|表示方式不變|表示方式不變|
|-0|1000|1111|0000(求解過程:[x]補 = 2^n - \x\ = 2^4 - \-0\ = 2^4 - (+0),使用二進制則為10000 - 0000 = 10000,超過4位(有進位),那么舍棄進位1,最終結果就是0000)|
|-1|1001|1110|1111|
|-2|1010|1101|1110|
|-3|1011|1100|1101|
|-4|1100|1011|1100|
|-5|1101|1010|1011|
|-6|1110|1001|1010|
|-7|1111|1000|1001|
|-8|超出4個bit所能表達范圍|超出4個bit所能表達范圍|1000|
|備注|零重碼,二進制存在兩種表示方法:0000和1000|零重碼,二進制存在兩種表示方法:0000和1111|零無重碼,同時解決了原碼和反碼不能表示-8的問題|

  • 通過上述表格,可以很自然的總結出一個結論:補碼表示法(two's complement representation)可以防止0的機器數重碼,同時又解決了原碼和反碼無法表示-8的問題,這樣就極大的簡化了計算機的硬件設計。

  • 結合之前提到的時鐘例子,我們把補碼表示法(two's complement representation)所表示的四位存儲單元,按照從0000到1111遞增的方式,均勻的分布在時鐘的表盤上。于是,我們就可以得到下面這張圖(圖片來自于這里):

two's complement wheel
  • OK,繼續以時鐘的方式來觀察上圖:

    • 順時針方向位加法,逆時針方向為減法
    • 模為2^n:在1111處順時針撥一格,就到了0000。用數學的方式,即1111 + 1 = 10000,進位舍棄則結果為0000,那么四位存儲的模就是10000(2^4)
    • 減法轉換為加法:3 - 1 = 3 + (-1) = 0011 + 1111 = 0010,眼尖的人可能會說0011 + 1111明明等于10010,怎么會是0010?還記得之前提過的最高位進位舍棄嘛,因此對于4位存儲來說,進位舍棄后就是0010 = 2。

      若減法不轉換為加法,那么3 -1 = 0011 - 0001 = 0010 = 2

    • 數據溢出:當0111(7)加1時,按照我們人的思維來說,應該結果為8,但是對于機器來說則不是,因為0111(7)是四位存儲所能表示的最大有符號數,所以它是無法表示01000(8)的,這個時候我們就說數據溢出了。那么數據溢出該怎么辦呢?很簡單,機器的思考方式顯然和我們人腦不一樣,機器按照上面環形圖的方式,由于0111(7)加1是順時針造成的數據溢出,那么我們可以把機器的操作想象成在0111(7)處順時針撥了一格,我們再去對照下環形圖發現這時候指向了1000(-8)。

      把這個過程想象成撥時針就OK了,對于1000(-8)減1也是同樣道理

  • 至此,我們完全可以總結一下,并解答計算機為什么用補碼存儲數據:

    1. 計算機只有加法器沒有減法器,兩個數的減法運算會被計算機轉換為加法運算,而補碼正好能夠解決減法轉換為加法的問題
    2. 防止機器發生零重碼,同時解決了原碼和反碼不能表示-8的問題,這樣極大的簡化了計算機的硬件設計
    3. 以循環的方式解決數據溢出的問題

從補碼的角度解答代碼片中的數據溢出

  • 既然已經知道了計算機為什么用補碼存儲數據,那我們就可以回過頭去消滅文章開頭的數據溢出的代碼片了。由于代碼片中ch和i的問題是一樣的,那我們就選擇ch來進行分析,另一個留給你們分析。

  • 在Java中,char為無符號數,16位存儲,表示范圍是0~2^16-1(即0~65535)。

    1. 首先,我們按照0000 0000 0000 0000到1111 1111 1111 1111遞增的方式,均勻的分布在時鐘的表盤上,圖就不畫了,自己在腦中想象一下或者畫個草稿。
    2. 然后,找出數據溢出點,通過觀察char環形圖可以發現數據溢出點是0(0000 0000 0000 0000)和65535(1111 1111 1111 1111)
    3. 最后,我們的ch = 65535 + 1,那么很顯然發生了數據溢出,按照撥時針的方式就可以得出ch = 0
  • Perfect,是否解答了當初學操作系統和編程的時候,困擾你們很久的問題。送給大家一句話:有些概念可能當時不理解,但是隨著經驗多累積和回顧的多了,自然而然就理解了。

貼出我看的關于補碼的文章鏈接,有幾篇中文文章對于某些知識點可能說錯了,切記要帶著批判的觀點去看:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容