爲什麼計算機中的負數要用補碼錶示?

前言

大家好,我是小彭。

在前面的文章裏,我們聊到了計算機的馮·諾依曼架構的 3 個基本原則。其中第 1 個原則是計算機中所有信息都是採用二進制格式的編碼。也就是說,在計算機中程序的數據和指令,以及用戶輸入的所有數據,計算機都需要把它們轉換爲二進制的格式,才能進行識別和運算。

然而,我們日常生活接觸到的大部分數字卻是十進制編碼,例如手機號碼、工牌號、學號。那爲什麼計算機要使用二進制數制?二進制數據如何進行運算,以及計算機做了哪些優化來如何提高運算的效率?今天我們就圍繞這些問題展開。


思維導圖:


1. 爲什麼計算機要使用二進制數制?

所謂數制其實就是一種 “計數的進位方式”。

常見的數制有十進制、二進制、八進制和十六進制:

  • 十進制是我們日常生活中最熟悉的進位方式,它一共有 0、1、2、3、4、5、6、7、8 和 9 十個符號。在計數的過程中,當某一位滿 10 時,就需要向它臨近的高位進一,即逢十進一;

  • 二進制是程序員更熟悉的進位方式,也是隨着計算機的誕生而發展起來的,它只有 0 和 1 兩個符號。在計數的過程中,當某一位滿 2 時,就需要向它臨近的高位進一,即逢二進一;

  • 八進制和十六進制同理。

那麼,爲什麼計算機要使用二進制數制,而不是人類更熟悉的十進制呢?其原因在於二進制只有兩種狀態,製造只有 2 個穩定狀態的電子元器件可以使用高低電位或有無脈衝區分,而相比於具備多個狀態的電子元器件會更加穩定可靠。


2.有符號數與無符號數

在計算機中會區分有符號數和無符號數,無符號數不需要考慮符號,可以將數字編碼中的每一位都用來存放數值。有符號數需要考慮正負性,然而計算機是無法識別符號的 “正+” 或 “負-” 標誌的,那怎麼辦呢?

好在我們發現 “正 / 負” 是兩種截然不同的狀態,正好可以映射到計算機能夠理解的 “0 / 1” 上。因此,我們可以直接 “將符號數字化”,將 “正+” 數字化爲 “0”,將 “負-” 數字化爲 “1”,並將數字化後的符號和數值共同組成數字編碼。

另外,爲了計算方便,我們額外再規定將 “符號位” 放在數字編碼的 “最高位”。例如,+1110-1110 用 8 位二進制表示就是:

  • 0000, 1110(符號作爲編碼的一部分,最高位 0 表示正數)
  • 1000, 1110(符號作爲編碼的一部分,最高位 1 表示負數)

從中我們也可以看出無符號數和有符號數的區別:

  • 1、最高位功能不同: 無符號數的編碼中的每一位都可以用來存放數值信息,而有符號數需要在編碼的最高位留出一位符號位;

  • 2、數值範圍不同: 相同位數下有符號數和無符號數表示的數值範圍不同。以 16 位數爲例,無符號數可以表示 0~65536,而有符號數可以表示 -32768~32768。

提示: 無符號數和有符號數表示的數值範圍大小是一樣大的,n 位二進制最多隻能表示 2^n 個信息量,這是無法被突破的。


3. 機器數的運算效率問題

在計算機中,我們會把帶 “正 / 負” 符號的數稱爲真值(True Value),而把符號化後的數稱爲機器數(Computer Number)。

機器數纔是數字在計算機中的二進制表示。 例如在前面的數字中, +1110 是真值,而 0000, 1110 是機器數。新的問題來了:將符號數字化後的機器數,在運算的過程中符號位是否與數值參與運算,又應該如何運算呢?

我們先舉幾個加法運算的例子:

  • 兩個正數相加:
0000, 1110 + 0000, 0001 = 0000, 1111 // 14 + 1 = 15 正確
^            ^            ^
符號位        符號位        符號位
  • 兩個負數相加:
1000, 1110 + 1000, 0001 = 0000, 1111 // (-14) + (-1) = 15 錯誤
^            ^            ^
符號位        符號位        符號位(最高位的 1 溢出)
  • 正負數相加:
0000, 1110 + 1000, 0001 = 1001, 1111 // 14 + (-1) = -15 錯誤
^            ^            ^
符號位        符號位        符號位

可以看到,在對機器數進行 “按位加法” 運算時,只有兩個正數的加法運算的結果是正確的,而包含負數的加法運算的結果卻是錯誤的,會出現 -14 - 1 = 1514 - 1 = -15 這種錯誤結果。

所以,帶負數的加法運算就不能使用常規的按位加法運算了,需要做特殊處理:

  • 兩個正數相加:

    • 直接做按位加法。
  • 兩個負數相加:

    • 1、用較大的絕對值 + 較小的絕對值(加法運算);
    • 2、最終結果的符號爲負。
  • 正負數相加:

    • 1、判斷兩個數的絕對值大小(數值部分);
    • 2、用較大的絕對值 - 較小的絕對值(減法運算);
    • 3、最終結果的符號取絕對值較大數的符號。

哇🤩?好好的加法運算給整成減法運算? 運算器的電路設計不僅要多設置一個減法器,而且運算步驟還特別複雜。那麼,有沒有不需要設置減法器,而且步驟簡單的方案呢?


4. 原碼、反碼、補碼

爲了解決有符號機器數運算效率問題,計算機科學家們提出多種機器數的表示法:

機器數 正數 負數
原碼 符號位表示符號
數值位表示真值的絕對值
符號位表示數字的符號
數值位表示真值的絕對值
反碼 無(或者認爲是原碼本身) 符號位爲 1
數值位是對原碼數值位的 “按位取反”
補碼 無(或者認爲是原碼本身) 在負數反碼的基礎上 + 1
  • 1、原碼: 原碼是最簡單的機器數,例如前文提到從 +1110-1110 轉換得到的 0000, 11101000, 1110 就是原碼錶示法,所以原碼在進行數字運算時會存在前文提到的效率問題;

  • 2、反碼: 反碼一般認爲是原碼和補碼轉換的中間過渡;

  • 3、補碼: 補碼纔是解決機器數的運算效率的關鍵, 在計算機中所有 “整型類型” 的負數都會使用補碼錶示法;

    • 正數的補碼是原碼本身;
    • 零的補碼是零;
    • 負數的補碼是在反碼的基礎上再加 1。

很多教材和網上的資料會認爲正數的原碼、反碼和補碼是相同的,這麼說倒也不影響什麼。 但結合補碼的設計原理,小彭的觀點是正數是沒有反碼和補碼的,負數使用補碼是爲了找到一個 “等價” 的正補數代替負數參與計算,將加減法運算統一爲兩個正數加法運算,而正數自然是不需要替換的,所以也就沒有補碼的形式。

提示: 爲了便於你理解,小彭後文會繼續用 “正數的補碼是原碼本身” 這個觀點闡述。


5. 使用補碼消除減法運算

理解補碼錶示法後,似乎還是不清楚補碼有什麼用❓

我們重新計算上一節的加法運算試試:

舉例 真值 原碼 反碼 補碼
+14 +1110 0000, 1110 0000, 1110 0000, 1110
+13 +1101 0000, 1101 0000, 1101 0000, 1101
-14 +1110 1000, 1110 1111, 0001 1111, 0010
-15 -1110 1000, 1111 1111, 0000 1111, 0001
+1 +0001 0000, 0001 0000, 0001 0000, 0001
-1 -0001 1000, 0001 1111, 1110 1111, 1111
  • 兩個正數相加:
// 補碼錶示法
0000, 1110 + 0000, 0001 = 0000, 1111 // 14 + 1 = 15 正確
^            ^            ^
符號位        符號位        符號位
  • 兩個負數相加:
// 補碼錶示法
1111, 0010 + 1111, 1111 = 1111, 0001 // (-14) + (-1) = -15 正確
^            ^            ^
符號位        符號位        符號位(最高位的 1 溢出)
  • 正負數相加:
// 補碼錶示法
0000, 1110 + 1111, 1111 = 0000, 1101 // 14 + (-1) = 13 正確
^            ^            ^
符號位        符號位        符號位(最高位的 1 溢出)

可以看到,使用補碼錶示法後,有符號機器數加法運算就只是純粹的加法運算,不會因爲符號的正負性而採用不同的計算方法,也不需要減法運算。因此電路設計中只需要設置加法器和補數器,就可以完成有符號數的加法和減法運算,能夠簡化電路設計。

除了消除減法運算外,補碼錶示法還實現了 “0” 的機器數的唯一性:

在原碼錶示法中,“+0” 和 “-0” 都是合法的,而在補碼錶示法中 “0” 只有唯一的機器數表示,即 0000, 0000 。換言之補碼能夠比原碼多表示一個最小的負數 1000, 0000

最後提供按照不同表示法解釋二進制機器數後得到的真值對比:

二進制數 無符號真值 原碼真值 反碼真值 補碼真值
0000, 0000 0 +0 +0 +0
0000, 0001 1 +1 +1 +1
1000, 0000 128 -0(負零,無意義) -127 -128(多表示一個數)
1000, 0001 129 -1 -126 -127
1111, 1110 254 -126 -1 -2
1111, 1111 255 -127 -0(負零) -1

6. 補碼我懂了,但是爲什麼?

理解原碼和補碼的定義不難,理解補碼作用也不難,難的是理解補碼是怎麼設計出來的,總不可能是被樹上的蘋果砸到後想到的吧?

這就要提到數學中的 “補數” 概念:

  • 1、當一個正數和一個負數互爲補數時,它們的絕對值之和就是模;
  • 2、一個負數可以用它的正補數代替。

6.1 時鐘裏的補數

聽起來很抽象對吧❓其實生活中,就有一個更加形象的例子 —— 時鐘,時鐘裏就蘊含着補數的概念!

比如說,現在時鐘的時針刻度指向 6 點,我們想讓它指向 3 點,應該怎麼做:

  • 方法 1 : 逆時針地撥動 3 個點數,讓時針指向 3 點,這相當於做減法運算 -3;
  • 方法 2: 順時針地撥動 9 個點數,讓時針指向 3 點,這相當於做加法運算 +9。

可以看到,對於時鐘來說 -3 和 +9 竟然是等價的! 這是因爲時鐘只能 12 個小時,當時間點數超過 12 時就會自動丟失,所以 15 點和 3 點在時鐘看來是都是 3 點。如果我們要在時鐘上進行 6 - 3 減法運算,我們可以將 -3 等價替換爲它的正補數 +9 後參與計算,從而將減法運算替換爲 6 + 9 加法運算,結果都是 3。

6.2 十進制的例子

理解了補數的概念後,我們再多看一個十進制的例子:我們要計算十進制 354365 - 95937 = 的結果,怎麼做呢?

  • 方法 1 - 借位做減法: 常規的做法是利用連續向前借位做減法的方式計算,這沒有問題;
  • 方法 2 - 減模加補: 使用補數的概念後,我們就可以將減法運算消除爲加法運算。

具體來說,如果我們限制十進制數的位長最多隻有 6 位,那麼模就是 1000000,-95937 對應的正補數就是 1000000 - 95937 = 904063 。此時,我們可以直接用正補數代替負數參與計算,則有:

354365 - 95937 // = 258428

= 354365 - (1000000 - 904063)

= 354365 - 1000000 + 904063 【減整加補】

= 258428

可以看到,把 -95937 等價替換爲 +904063 後,就把減法運算替換爲加法運算。細心的你可能要舉手提問了,還是需要減去 1000000 呀?🙋🏻♀️

其實並不用,因爲 1000000 是超過位數限制的,所以減去 1000000 這一步就像時針逆時針撥動一整圈一樣是無效的。所以實際上需要計算的是:

// 實際需要計算的是:
354365 + 904063
= 1258428 = 258428
  ^
  最高位 1 超出位數限制,直接丟棄

6.3 爲什麼要使用補碼?

繼續使用前文提到的 14 + (-1) 正負數相加的例子:

// 原碼錶示法
0000, 1110 + 1000, 0001 = 1001, 1111 // 14 + (-1) = -15 錯誤
^            ^            ^
符號位        符號位        符號位

// 補碼錶示法
0000, 1110 + 1111, 1111 = 1, 0000, 1101 // 14 + (-1) = 13 正確
^            ^            ^
符號位        符號位        最高位 1 超出位數限制,直接丟棄

如果我們限制二進制數字的位長最多隻有 8 位,那麼模就是 1, 0000, 0000 ,此時,-1 的二進制數 1000, 0001 的正補數就是 1111, 1111

我們使用正補數 1111, 1111 代替負數 1000, 0001 參與運算,加法運算後的結果是 1, 0000, 1101。其中最高位 1 超出位數限制,直接丟棄,所以最終結果是 0000, 1101,也就是 13,計算正確。

補碼示意圖

到這裏,相信補碼的設計原理已經很清楚了。

補碼的關鍵在於:找到一個與負數等價的正補數,使用該正補數代替負數,從而將減法運算替換爲兩個正數加法運算。 補碼的出現與運算器的電路設計有關,從設計者的角度看,希望儘可能簡化電路設計和計算複雜度。而使用正補數代替負數就可以消除減法器,實現簡化電路的目的。

所以,小彭認爲只有負數才存在補碼,正數本身就是正數,根本就沒必要使用補數,更不需要轉爲補碼。而且正數使用補碼的話,還不能把負數轉補碼的算法用在正數上,還得強行加一條 “正數的補碼是原碼本身” 的規則,就離譜好吧。


7. 總結

  • 1、無符號數的編碼中的每一位都可以用來存放數值信息,而有符號數需要在最高位留出一位符號位;

  • 2、在有符號數的機器數運算中,需要對正數和負數採用不同的計算方法,而且需要引入減法器;

  • 3、爲了解決有符號機器數運算效率問題,計算機科學家們提出多種機器數的表示法:原碼、反碼、補碼和移碼;

  • 4、使用補碼錶示法後,運算器可以消除減法運算,而且實現了 “0” 的機器數的唯一性;

  • 5、補碼的關鍵是找到一個與負數等價的正補數,使用該正補數代替負數參與計算,從而將減法運算替換爲加法運算。

在前文講補碼的地方,我們提到計算機所有 “整型類型” 的負數都會使用補碼錶示法,刻意強調 “整數類型” 是什麼原因呢,難道浮點數和整數在計算機中的表示方法不同嗎?這個問題我們在 下一篇文章 裏討論,請關注。


參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章