前言
大家好,我是小彭。
在前面的文章裏,我們聊到了計算機的馮·諾依曼架構的 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 位二進制最多隻能表示 個信息量,這是無法被突破的。
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 = 15
和 14 - 1 = -15
這種錯誤結果。
所以,帶負數的加法運算就不能使用常規的按位加法運算了,需要做特殊處理:
-
兩個正數相加:
- 直接做按位加法。
-
兩個負數相加:
- 1、用較大的絕對值 + 較小的絕對值(加法運算);
- 2、最終結果的符號爲負。
-
正負數相加:
- 1、判斷兩個數的絕對值大小(數值部分);
- 2、用較大的絕對值 - 較小的絕對值(減法運算);
- 3、最終結果的符號取絕對值較大數的符號。
哇🤩?好好的加法運算給整成減法運算? 運算器的電路設計不僅要多設置一個減法器,而且運算步驟還特別複雜。那麼,有沒有不需要設置減法器,而且步驟簡單的方案呢?
4. 原碼、反碼、補碼
爲了解決有符號機器數運算效率問題,計算機科學家們提出多種機器數的表示法:
機器數 | 正數 | 負數 |
---|---|---|
原碼 | 符號位表示符號 數值位表示真值的絕對值 |
符號位表示數字的符號 數值位表示真值的絕對值 |
反碼 | 無(或者認爲是原碼本身) | 符號位爲 1 數值位是對原碼數值位的 “按位取反” |
補碼 | 無(或者認爲是原碼本身) | 在負數反碼的基礎上 + 1 |
1、原碼: 原碼是最簡單的機器數,例如前文提到從
+1110
和-1110
轉換得到的0000, 1110
和1000, 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、補碼的關鍵是找到一個與負數等價的正補數,使用該正補數代替負數參與計算,從而將減法運算替換爲加法運算。
在前文講補碼的地方,我們提到計算機所有 “整型類型” 的負數都會使用補碼錶示法,刻意強調 “整數類型” 是什麼原因呢,難道浮點數和整數在計算機中的表示方法不同嗎?這個問題我們在 下一篇文章 裏討論,請關注。
參考資料
- 計算機組成原理教程(第 2、6 章) —— 尹豔輝 王海文 邢軍 著
- 深入淺出計算機組成原理(第 11 ~ 16 講) —— 徐文浩 著,極客時間 出品
- 10分鐘速成課 計算機科學 —— Carrie Anne 著
- Binary number —— Wikipedia