標籤:

計算機補碼運算背後的數學原理是什麼?

只會用,但不知道背後的數學原理是什麼。同餘或者模的思想怎麼解釋帶符號位運算?


不知道純轉行不行,但題主真的不能先百度一下嗎?

以下是轉的內容:

作者:張子秋

出處:ziqiu.zhang - 博客園

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

一. 機器數和真值

在學習原碼, 反碼和補碼之前, 需要先了解機器數和真值的概念.

1、機器數

一個數在計算機中的二進位表示形式, 叫做這個數的機器數。機器數是帶符號的,在計算機用一個數的最高位存放符號, 正數為0, 負數為1.

比如,十進位中的數 +3 ,計算機字長為8位,轉換成二進位就是00000011。如果是 -3 ,就是 10000011 。

那麼,這裡的 00000011 和 10000011 就是機器數。

2、真值

因為第一位是符號位,所以機器數的形式值就不等於真正的數值。例如上面的有符號數 10000011,其最高位1代表負,其真正數值是 -3 而不是形式值131(10000011轉換成十進位等於131)。所以,為區別起見,將帶符號位的機器數對應的真正數值稱為機器數的真值。

例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1

二. 原碼, 反碼, 補碼的基礎概念和計算方法.

在探求為何機器要使用補碼之前, 讓我們先了解原碼, 反碼和補碼的概念.對於一個數, 計算機要使用一定的編碼方式進行存儲. 原碼, 反碼, 補碼是機器存儲一個具體數字的編碼方式.

1. 原碼

原碼就是符號位加上真值的絕對值, 即用第一位表示符號, 其餘位表示值. 比如如果是8位二進位:

[+1]原 = 0000 0001

[-1]原 = 1000 0001

第一位是符號位. 因為第一位是符號位, 所以8位二進位數的取值範圍就是:

[1111 1111 , 0111 1111]

[-127 , 127]

原碼是人腦最容易理解和計算的表示方式.

2. 反碼

反碼的表示方法是:

正數的反碼是其本身

負數的反碼是在其原碼的基礎上, 符號位不變,其餘各個位取反.

[+1] = [00000001]原 = [00000001]反

[-1] = [10000001]原 = [11111110]反

可見如果一個反碼錶示的是負數, 人腦無法直觀的看出來它的數值. 通常要將其轉換成原碼再計算.

3. 補碼

補碼的表示方法是:

正數的補碼就是其本身

負數的補碼是在其原碼的基礎上, 符號位不變, 其餘各位取反, 最後+1. (即在反碼的基礎上+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]補

[-1] = [10000001]原 = [11111110]反 = [11111111]補

對於負數, 補碼錶示方式也是人腦無法直觀看出其數值的. 通常也需要轉換成原碼在計算其數值.

三. 為何要使用原碼, 反碼和補碼

在開始深入學習前, 我的學習建議是先"死記硬背"上面的原碼, 反碼和補碼的表示方式以及計算方法.

現在我們知道了計算機可以有三種編碼方式表示一個數. 對於正數因為三種編碼方式的結果都相同:

[+1] = [00000001]原 = [00000001]反 = [00000001]補

所以不需要過多解釋. 但是對於負數:

[-1] = [10000001]原 = [11111110]反 = [11111111]補

可見原碼, 反碼和補碼是完全不同的. 既然原碼才是被人腦直接識別並用於計算表示方式, 為何還會有反碼和補碼呢?

首先, 因為人腦可以知道第一位是符號位, 在計算的時候我們會根據符號位, 選擇對真值區域的加減. (真值的概念在本文最開頭). 但是對於計算機, 加減乘數已經是最基礎的運算, 要設計的盡量簡單. 計算機辨別"符號位"顯然會讓計算機的基礎電路設計變得十分複雜! 於是人們想出了將符號位也參與運算的方法. 我們知道, 根據運演算法則減去一個正數等於加上一個負數, 即: 1-1 = 1 + (-1) = 0 , 所以機器可以只有加法而沒有減法, 這樣計算機運算的設計就更簡單了.

於是人們開始探索 將符號位參與運算, 並且只保留加法的方法. 首先來看原碼:

計算十進位的表達式: 1-1=0

1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

如果用原碼錶示, 讓符號位也參與計算, 顯然對於減法來說, 結果是不正確的.這也就是為何計算機內部不使用原碼錶示一個數.

為了解決原碼做減法的問題, 出現了反碼:

計算十進位的表達式: 1-1=0

1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

發現用反碼計算減法, 結果的真值部分是正確的. 而唯一的問題其實就出現在"0"這個特殊的數值上. 雖然人們理解上+0和-0是一樣的, 但是0帶符號是沒有任何意義的. 而且會有[0000 0000]原和[1000 0000]原兩個編碼表示0.

於是補碼的出現, 解決了0的符號以及兩個編碼的問題:

1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]補 + [1111 1111]補 = [0000 0000]補=[0000 0000]原

這樣0用[0000 0000]表示, 而以前出現問題的-0則不存在了.而且可以用[1000 0000]表示-128:

(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]補 + [1000 0001]補 = [1000 0000]補

-1-127的結果應該是-128, 在用補碼運算的結果中, [1000 0000]補 就是-128. 但是注意因為實際上是使用以前的-0的補碼來表示-128, 所以-128並沒有原碼和反碼錶示.(對-128的補碼錶示[1000 0000]補算出來的原碼是[0000 0000]原, 這是不正確的)

使用補碼, 不僅僅修復了0的符號以及存在兩個編碼的問題, 而且還能夠多表示一個最低數. 這就是為什麼8位二進位, 使用原碼或反碼錶示的範圍為[-127, +127], 而使用補碼錶示的範圍為[-128, 127].

因為機器使用補碼, 所以對於編程中常用到的32位int類型, 可以表示範圍是: [-2

31

, 2

31

-1] 因為第一位表示的是符號位.而使用補碼錶示時又可以多保存一個最小值.四 原碼, 反碼, 補碼 再深入

計算機巧妙地把符號位參與運算, 並且將減法變成了加法, 背後蘊含了怎樣的數學原理呢?

將鐘錶想像成是一個1位的12進位數. 如果當前時間是6點, 我希望將時間設置成4點, 需要怎麼做呢?我們可以:

1. 往回撥2個小時: 6 - 2 = 4

2. 往前撥10個小時: (6 + 10) mod 12 = 4

3. 往前撥10+12=22個小時: (6+22) mod 12 =4

2,3方法中的mod是指取模操作, 16 mod 12 =4 即用16除以12後的餘數是4.

所以鐘錶往回撥(減法)的結果可以用往前撥(加法)替代!

現在的焦點就落在了如何用一個正數, 來替代一個負數. 上面的例子我們能感覺出來一些端倪, 發現一些規律. 但是數學是嚴謹的. 不能靠感覺.

首先介紹一個數學中相關的概念: 同餘

同餘的概念

兩個整數a,b,若它們除以整數m所得的餘數相等,則稱a,b對於模m同餘

記作 a ≡ b (mod m)

讀作 a 與 b 關於模 m 同餘。

舉例說明:

4 mod 12 = 4

16 mod 12 = 4

28 mod 12 = 4

所以4, 16, 28關於模 12 同餘.

負數取模

正數進行mod運算是很簡單的. 但是負數呢?

下面是關於mod運算的數學定義:

上面是截圖, "取下界"符號找不到如何輸入(word中粘貼過來後亂碼). 下面是使用"L"和"J"替換上圖的"取下界"符號:

x mod y = x - y L x / y J

上面公式的意思是:

x mod y等於 x 減去 y 乘上 x與y的商的下界.

以 -3 mod 2 舉例:

-3 mod 2

= -3 - 2xL -3/2 J

= -3 - 2xL-1.5J

= -3 - 2x(-2)

= -3 + 4 = 1

所以:

(-2) mod 12 = 12-2=10

(-4) mod 12 = 12-4 = 8

(-5) mod 12 = 12 - 5 = 7

開始證明

再回到時鐘的問題上:

回撥2小時 = 前撥10小時

回撥4小時 = 前撥8小時

回撥5小時= 前撥7小時

注意, 這裡發現的規律!

結合上面學到的同餘的概念.實際上:

(-2) mod 12 = 10

10 mod 12 = 10

-2與10是同餘的.

(-4) mod 12 = 8

8 mod 12 = 8

-4與8是同餘的.

距離成功越來越近了. 要實現用正數替代負數, 只需要運用同餘數的兩個定理:

反身性:

a ≡ a (mod m)

這個定理是很顯而易見的.

線性運算定理:

如果a ≡ b (mod m),c ≡ d (mod m) 那麼:

(1)a ± c ≡ b ± d (mod m)

(2)a * c ≡ b * d (mod m)

如果想看這個定理的證明, 請看:同餘_百度百科

所以:

7 ≡ 7 (mod 12)

(-2) ≡ 10 (mod 12)

7 -2 ≡ 7 + 10 (mod 12)

現在我們為一個負數, 找到了它的正數同餘數. 但是並不是7-2 = 7+10, 而是 7 -2 ≡ 7 + 10 (mod 12) , 即計算結果的餘數相等.

接下來回到二進位的問題上, 看一下: 2-1=1的問題.

2-1=2+(-1) = [0000 0010]原 + [1000 0001]原= [0000 0010]反 + [1111 1110]反

先到這一步, -1的反碼錶示是1111 1110. 如果這裡將[1111 1110]認為是原碼, 則[1111 1110]原 = -126, 這裡將符號位除去, 即認為是126.

發現有如下規律:

(-1) mod 127 = 126

126 mod 127 = 126

即:

(-1) ≡ 126 (mod 127)

2-1 ≡ 2+126 (mod 127)

2-1 與 2+126的餘數結果是相同的! 而這個餘數, 正式我們的期望的計算結果: 2-1=1

所以說一個數的反碼, 實際上是這個數對於一個膜的同餘數. 而這個膜並不是我們的二進位, 而是所能表示的最大值! 這就和鐘錶一樣, 轉了一圈後總能找到在可表示範圍內的一個正確的數值!

而2+126很顯然相當於鐘錶轉過了一輪, 而因為符號位是參與計算的, 正好和溢出的最高位形成正確的運算結果.

既然反碼可以將減法變成加法, 那麼現在計算機使用的補碼呢? 為什麼在反碼的基礎上加1, 還能得到正確的結果?

2-1=2+(-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]補 + [1111 1111]補

如果把[1111 1111]當成原碼, 去除符號位, 則:

[0111 1111]原 = 127

其實, 在反碼的基礎上+1, 只是相當於增加了膜的值:

(-1) mod 128 = 127

127 mod 128 = 127

2-1 ≡ 2+127 (mod 128)

此時, 錶盤相當於每128個刻度轉一輪. 所以用補碼錶示的運算結果最小值和最大值應該是[-128, 128].

但是由於0的特殊情況, 沒有辦法表示128, 所以補碼的取值範圍是[-128, 127]


一、序言

第一版答案寫於2016年8月,當時我正試圖理解補碼規則的邏輯,並用結果寫了一篇回答發在知乎和公眾號上,因為收到的回復很樂觀,讓我一度認為已經把握問題的全貌。事實上答案在符號位的論述上存在謬誤,多虧知友在回復中指出,為此我進行了更深入的思考,重新編輯此答案,希望能更接近問題的本原。

二、重溫運算規則

首先我想把整套關於原碼反碼補碼的運算規則準確清晰地寫一遍,方便急需應用的知友參考,也希望大家首先能記住這套規定,再開始進一步的探討。

所謂原碼就是機器數,是加了一位符號位的二進位數,正數符號位為0,負數符號位為1,計算機中存儲、處理、運算的數據通常是8位、16位、32位或64位的,這裡以最簡單的8位為例講解。注意符號位是包含在8位中的其中1位,故可直觀讀出的數只有7位(只有後7位數可以按權展開)。有心人可能注意到原碼是有缺陷的,它只能表示255種狀態,因為00000000(+0)和10000000(-0)其實是一個數,因此原碼的表示範圍成了-127到+127,這個問題需要神奇的補碼來解決,因為在補碼中10000000被用來表示-128。

所謂反碼,英語里又叫ones" complement(對1求補),這裡的1,本質上是一個有限位計數系統里所能表示出的最大值,在8位二進位里就是11111111,在1位十進位里就是9,在3位十六進位里就是FFF(再大就要進位了)。求反又被稱為對一求補,用最大數減去一個數就能得到它的反,很容易看出在二進位里11111111減去任何數結果都是把這個數按位取反,0變1,1變零,所以才稱之為反碼。用原碼求反碼的方法是,正數不變,負數保留符號位1不變,剩下位按位取反。

所謂補碼,英語里又叫two"s complement(對2求補),這個2指的是計數系統的容量(模),就是計數系統所能表示的狀態數。對1位二進位數來說只有0和1兩種狀態,所以模是10也就是十進位的2,對7位二進位數來說就是10000000,這個模是不可能取到的,因為位數多一位。用模減去一個數(無符號部分)就能得到這個數的補,比如10000000-1010010=0101110,事實上因為10000000=1111111+1,稍加改變就成了(1111111-1010010)+1,所以又可以表述為先求反再加1。總結求補碼的方法就是正數依舊不變,負數保留符號位不變,先求反碼再加上1。

記住了怎麼求補碼,接下來講講運算。通過原碼的符號位和數值,我們能迅速指出它代表的數,判斷其正負並進行四則運算,相比而言反碼和補碼對於人則顯得過於晦澀。如果說原碼是給人看的數字語言,那麼補碼就是計算機的數字語言。計算機不需要知道什麼是正負、大小,這些判斷對它而言過於複雜。事實上它存儲、處理、傳輸的數都只有補碼一種形式,人所做的加減乘除,在計算機里只通過相加和移位就能解決,這都來自於補碼系統的內在自洽和巧奪天工的神奇魔力,也是後文要闡述的重點。

對加法和減法,按上文的方法求得補碼之後,直接相加就可以了,但相加的時候符號位一定要一起參與運算,有時候,兩符號位相加或者接受來自低位的進位會發生溢出,就扔掉溢出的一位(稍後會解釋為什麼),由新的符號位決定結果的正負,如果是0表示正數,結果就是原碼,如果是1表示負數,結果還要再求補數得到原碼。

至此我介紹了原碼反碼補碼的規定,以及如何求補碼並進行加減法(乘除暫不涉及,事實上懂了加減法的奧秘,乘除很容易理解),對於一個工程人才來說,上面的內容已經足夠應付所有具體問題。剩下的則是一些「無用」的思考,關於為何這套法則能夠化減為加,以及人為規定的符號位在運算中為何總是能精確地指示結果的符號。

三、無用之用

數字是用來記錄現實世界數量屬性的語言。

而任何計數系統都必須有兩個參數:容量精度

是衡量計數系統容量的參數。模代表了計數系統所能表示和存儲的狀態數。

任何有限的計數系統都有一個確定的模。如時鐘的模是12(即只有一個位的十二進位系統,若再加一個大鐘,使小鍾轉一周大鐘加一刻度,就是有兩個位的十二進位系統),再比如8位計算機的模是2^8=256D(每一位也可以單獨看做一個模為2的計數系統)。

問題一:化減為加

對同一計數系統中的數量可以定義運算如加減,但運算結果超出預設位數時,就要發生溢出,這個溢出其實就是模,是時鐘的一整圈(因此丟掉它沒有影響),如果進位沒有被另一個計數系統接受,結果看似「失真」,本質上是進入了「第二次循環」。

以時鐘系統為例:8+7=15D=13(十二進位)&>10(十二進位),進位1溢出丟失(除非用另一個時鐘接收這個進位),在錶盤上(即一位十二進位計數系統中)呈現為3,而8-5=8+(-5)=3也得到了相同結果。這就說明在有限容量的計數系統中,+7和-5是完全相同的,而它們正是關於模12的一對補數。

因此我們在有限的計數系統做了這種定義:正數補數即為本身,負數A【補】=模-絕對值(A)。一個數加上另一個數(可以是正數也可以是負數),結果等於加上這個數的補數,若有進位則捨棄進位。這麼做的重大意義在於極大地方便了計算機進行數據處理,要知道對人而言減法並非難事,但用門電路實現就複雜得多了,減之前還要判斷大小考慮次序。

問題二:符號位參與運算

在8位計算機中,一個位元組可以表示256種狀態,把位元組看做一個鐘的話,刻度可以隨便標,不如取0點鐘為-128,正對的6點鐘為0,即存儲範圍是從-128到127,用二進位補碼錶示是10000000~01111111(10000000用來表示-128似乎是人為定義的,因為原碼無法表示-128,按正常程序更無法求得其補碼)。

符號位是我認為理解補碼的關鍵所在,也是關於補碼最神奇的地方。人類「生硬」地添加了符號位,把256種狀態剪成正負兩半,還「生硬」地規定-128的補碼為10000000,但用補碼運算的時候,一切就像「水往低處流」般正確和諧自然:符號位參與運算,接受來自低位的進位,永遠能忠實地指示結果的正負。

我舉個例子,你們感受一下:

所謂的「負數加負數會變成正數,正數加正數會變成負數」,本質還是在於,計數系統是無法表示超出其取值範圍的計算結果的。

120D+120D=01111000B+01111000B=11110000B,符號位的1來自低位進位,指示了結果是負數,所以需要求補得10010000B也就是-16D,放在鐘面上就是從120順時針旋轉120格到240的位置,只不過系統最大隻取到127,240的位置就是-16的位置,而且-16和240正是關於模256的一對補數。-120D-120D=16D也是一樣的道理。在有限的計數系統內,由於位數的限制,發生溢出的情況下無法得到計算真實值,得到的是真實值關於模的補數。

看到這裡是不是有那麼點味道呢,我給你們總結一下:加法都是從低位往高位做的,如果兩個數(補碼),後七位相加產生了進位,說明

又溢出了一次,每當溢出一次(就是越過了-128這個正負分界點),符號就要反一下,0變1,1變0。符號是1的,說明大得越界了,需要再求個補,用取值範圍內的負數表示結果;符號是0的,說明小得越界了,但由於正數的補數就是本身,就不必再求補了。

四、後記

從八月底的初稿到這篇文章,中間經歷了差不多四個月的時間,我對於補碼問題的認識也經歷了困惑到清晰到困惑到再清晰這一過程,其中修改超過十次,思考所花的時間更是不計其數。從參加考試的角度看,我熟記的運算規則早已足夠我應付所有題目,但我仍然不願意半途而廢,原因有二:

大一學習線性代數時,曾經掛過科,因為對於定理和公式背後的含義一無所知,而老師也不加講解,只一味讓我們死記做題。雖然很多同學都適應這種所謂的「工科數學學習」,然而這對我而言簡直如同夢魘,沒有理解內化如何能稱得上學習,不過是應付考試然後忘的精光罷了。我很幸運的是,在準備補考時讀到了網上廣為流傳的孟岩老師的文章《理解矩陣》,我記得那是一個冬天的晚上,讀完文章後我很興奮,一直到半夜都睡不著,這是我第一次體會到數學體系的和諧自洽以及數學的深刻性在工程中的巨大威力,從那以後我才逐漸找到了學習數學的樂趣。

《理解矩陣》中有一段話至今我還記得,現摘抄如下:

自從1930年代法國布爾巴基學派興起以來,數學的公理化、系統性描述已經獲得巨大的成功,這使得我們接受的數學教育在嚴謹性上大大提高。然而數學公理化的一個備受爭議的副作用,就是一般數學教育中直覺性的喪失。數學家們似乎認為直覺性與抽象性是矛盾的,因此毫不猶豫地犧牲掉前者。然而包括我本人在內的很多人都對此表示懷疑,我們不認為直覺性與抽象性一定相互矛盾,特別是在數學教育中和數學教材中,幫助學生建立直覺,有助於它們理解那些抽象的概念,進而理解數學的本質。反之,如果一味注重形式上的嚴格性,學生就好像被迫進行鑽火圈表演的小白鼠一樣,變成枯燥的規則的奴隸。

「枯燥的規則的奴隸」又何止是在數學教學中出現的呢?如果你在大學工科學習過,你會發現這些人簡直遍地都是,拿我在的浙大為例,有的是學生對課程並不理解,單靠考前突擊刷題就拿到90分以上的成績。

正是在這樣的情形下,我決定盡我所能重新思考學到的每一個重要知識,並將其中一部分寫成文章,一來有助於對思維的梳理,二來也是便於自己將來的回顧,倘若拙作還能對他人也有所幫助,從而使我給世界留下一些微不足道的影響,那真是幸甚了。


一個知友問的,回答到這裡方便更多人看到。

有不對請指正,有細節沒寫全請腦補~。

「碼」和「數」是兩個東西。

我們平時說出或寫出某「數」,一般都是在十進位下,用10個不同的「碼」(此處的「碼」還和原碼補碼反碼的概念不同)來表示。分別是0~9。超過9,也就是比最大的碼還大的數,採用進位的方式來表示。於是有了「位」的概念。即個位,十位,百位等等。

表達負數的時候由於為了與算術和代數符號當中減號或作差相互兼容,就在該數前面加上減號。

這種對一個數的寫法也可以被名門為一種編碼方式。在這種編碼方式下每個數都有一定的「位數」,即「長度」。在計算機當中規定一定的位數稱作「字長」,比如4位,8位,16位,32位等等。只不過,計算機的物理存儲數據是二進位方式。我們人類目前傳統的還是比較喜歡用10進位方式。並且,我們人類一般不會在書寫數字的時候遇到「字長」的概念。但是在念數字和記錄數據的時候,有一點點東西跟字長相關。比如西方愛把數據分成三位一組三位一組的方式,覺得比較好記憶。比如12"000讀成十二千,我們中國人愛以「萬」為字長,1"2000,讀作一萬二千。舉這個例子表示字長的概念不是很恰當。因為對人而言這只是一種表達習慣。但是對機器而言,「字長」則是計算的長度單位。設計計算機進行最簡單的加法運算,首先要考慮的就是加法器進行一次加法運算,位數是多長?加法器設計成多少位的加法器?等等。這就是計算機的字長。它和編碼方式一起,構成了計算機進行計算的基礎。

有了字長的概念才好說補碼。

在十進位下也有補碼的概念。插一句,談到補碼一定要先明確字長的長度。

補碼是用來表示負數的一種編碼方式。也是為了在計算機的核心加法器部分的設計避免減法操作,存儲數據的時候避免存儲負號。

舉個例子,假如我們模擬一個字長為4位的十進位計算機。(假設有某種機制,可以在1位上有10種穩定的狀態)

對兩個數進行求和運算:1234 和 -1234

如果用原碼,那麼我們需要用一個1位的寄存器來存儲和表示負號。假設就在4位的最左邊的最高位前用1表示負號,0表示正號。

則,原碼錶示上面兩個數:

0 1234

1 1234

然後計算機做求和,做加法的過程中。計算機發現,其中有一個數是負數,於是要切換為兩個正數相減的模式來運算。這很不方便。

於是,補碼被想出來了。

-1234和正1234相加不是等於0000嗎?在4位的字長當中的數,和1234相加為0000的,是不是唯一的一個數哪?很明顯8766和1234相加等於1"0000,後4位是0000。

那麼,8766就可以看著是-1234的一種編碼表示方式,被稱作「補碼」。

所以,補碼就是把數1234在一個字長內,補足為1"0000的數的編碼方式。也就是一個正數的相反數,在計算機內的表達方式。

加上符號位,正數0"1234的相反數的補碼錶示就是1"8766。

十進位里,-1234的反碼就是1"8765

所以,現在回到4位字長的2進位計算機里來看。補碼和反碼的構成方式是一樣的。0"0001的相反數是-1。

補碼錶示1"1111

反碼錶示1"1110

看出補碼與反碼的區別了嗎?

反碼就是正數的原碼的相反數的一種構成方式(正數本身的反碼就是它本身,這裡說的是負數的情況),構成方法是按照原碼碼元逐位取反。在十進位里,取反是個難以延伸的概念,其實是以最大的碼元9來減。

補碼就是正數的原碼的相反數的另一種編碼方式。它能把字長內的正數,補足為全是0。

全爪機手打,有點累~


其實也沒什麼數學原理,主要是因為實現的代價小。在整型數字系統中,能處理算術溢出就差不多了(溢出標誌,當然還有其它標誌)。

模256下的加減法,用0, 1, 2, …, 254, 255表示其值,或者用-128, -127, …, -1, 0, 1, 2, …,127是完全等價的。-128與128,-127與129,…,-2與254,-1與255可以互換而加減法的結果不變。從而,把8位(octet)的高半部分(即二進位的1000 0000到1111 1111)解釋為-128到-1,同樣也實現了模256的加減法,而且所需要的CPU加法運算器的電路實現與8位無符號整數並無不同。
實際上對於8比特的存儲單元,把它的取值[00000000, 11111111]解釋為[0, 255],或者[0, 254]、[-1],或者[0, 253]、[-2, -1],或者[0, 127]、[-128, -1],或者[0, 55]、[-200, -1]對於加法硬體實現並無不同。

而補碼可以表示與正數幾乎相同數目的負數。

具體的過程可以看CSAPP第二章。以下節選:

https://zh.wikipedia.org/wiki/%E4%BA%8C%E8%A3%9C%E6%95%B8


差不多看了三個小時關於補碼的回答,最後是看答主 @張天行 和 @iittttt 的回答弄明白的。

兩位答主都是從理解方式,思維方式的角度來的解釋的,而我先一直在推公式,根本理解不了,orz。。。。。感謝二位。

根據兩位答主的回答,我想說一下我的看法。如有不對,歡迎批評指出。

首先,計算機為什麼需要補碼?

因為既要表示正數又要表示負數,如果單純的想讓計算機以無符號數加減的方式來進行運算的話(即不管符號位符號位也跟其他一樣進行加減,還有就是我看多有些回答說引入補碼的原因是想把減法變成加法,這個我有點接受不了,也想不大明白),用原碼肯定不行,所以,就要引入一種新的數的表示方式,啦啦啦,所以補碼就完美的出現了(其實這個說法感覺不是很恰當,因為只是我們慣有的思維方式而已)。

正如答主 @張天行 描述的

數字是用來記錄現實世界數量屬性的語言。

而任何計數系統都必須有兩個參數:容量精度

是衡量計數系統容量的參數。模代表了計數系統所能表示和存儲的狀態數。

感覺這個說法打開了我認知世界的新天地。

首先假設,我們就有擁有256個數。

在以前我的認知範圍內,數是依次排列在數軸上的,也就是-128,-127。。。-1,0,1,2.。。。126,127 ,但是兩頭怎麼辦呢,127+1等於多少呢,這時我們可以把它首尾相連,圍成一個圈,即很多答主提到的時鐘。這樣,127的下一個數就是-128。好了,假設我們這個世界只有加法運算了,這個時候127+1=-128。

個人拙見,在只有純加法的世界中意味著只有正數,如果這個模是256的話,範圍就是0~255,1~256,2~257.。。。這樣我們在計算的時候就不考慮符號位了,因為壓根就沒有符號位。所以我們用00000000~11111111來表示256個數的話,可以是0~255,當然也可以是-128~127,-127~128,-2~253,2~257。所以在補碼中,可以理解為-128~-1代替了128~256。但是這樣計算機就可以愉快的不用理符號位了。這樣看來符號位的這個說法也是只為了在我們已有的思維方式的基礎上,便於我們理解罷了。

假如說我們用1~256來代替-128~127的話,理解都是一樣的,唯一不一樣的就是 補碼=反碼+1(對於負數) 補碼=模-絕對值 這兩個公式了,取而代之的是:

正數,負數(十進位中我們所定義的正數):補碼=原碼+1

如果用-124~131代替,則是:

正數:補碼=原碼+4

負數:補碼=模-(絕對值+4) 這個是定義式

補碼=(原碼+4)的反碼 + 1 這個只是便於計算的公式,其實原碼和反碼只是過渡而已

that"s all


補碼的代數原理在這頁下半部分:補碼_百度百科

要特別注意的是,所謂補碼符號位直接參与運算自動得到正確結果是有前提條件的,這個條件就是不溢出

溢出的定義有三種,最好理解的是:兩個符號相同的補碼數相加,如果和的符號與加數的符號相反,或兩個符號相反的補碼數相減,差的符號與減數的符號相同,都屬於運算結果溢出。

溢出了怎麼辦?溢出後代數結果已經不可能正確了,為了合理解釋加法電路的結果,我們定義這個結果的數學含義是「代數結果在定義區間『-128--127』內的補數」。


在計算機中只有加法電路,但我們使用計算機的時候可以做減法,其實背後的數學原理很簡單。最近在看Digital Fundementals, 書上關於這些細節講的很透徹,建議大家儘可能看英文原版書。

首先我們來看十進位數的一個規律,

0+9+1=10

1+8+1=10

2+7+1=10

...

可以看到末端和首端相加其結果總是10等於兩位數10。

來看加上符號位共三位的二進位負數101b,其補碼為111b,除去符號位01b+11b=100b,可以看到,二進位負數除去符號位原碼和補碼相加和十進位數是相同的原理。所以二進位負數111b轉為正數只需要符號位參運算即可,即-1?2的2次方加上1?2的1次方等於-1。

11-01=(011+111)補碼=1010補碼

因為最高位1溢出,符號位為0,所以結果為10


參見《Computer Systems A Programmer"s Perspective》第二章。非常詳細,非常經典


推薦閱讀:

當今最好的本地文本檢索軟體是?
如果現在微軟重寫Windows會怎麼樣?
為什麼分布在地球各地的古文明大多採用十進位的計數法?
電腦發展史上有哪些偉大的思想和技術?
一門編程語言是如何被創造出來的?

TAG:數學 | 計算機 |