1. 機器數和真值
2. 原碼、反碼和補碼的介紹
3. 原碼、反碼和補碼的作用
4. 進制轉換
1. 機器數和真值
1.1 機器數
一個數在計算機中的二進制表示形式,叫做這個數的機器數。機器數是帶符號的,在計算機中用一個數的最高位存放符號,正數為 0、負數為 1。
比如,十進制中的數 +3 ,若計算機字長為 8 位,則轉換成二進制就是 00000011。如果是 -3 ,就是 10000011 。那么,這里的 00000011 和 10000011 就是機器數。
1.2 真值
因為第一位是符號位,所以機器數的形式值就不等於真正的數值。例如上面的有符號數 10000011,其最高位 1 代表負,其真正數值是 -3 而不是形式值 131(10000011 轉換成十進制等於 131)。所以,為區別起見,將帶符號位的機器數對應的真正數值稱為機器數的真值。
例:0000 0001 的真值 = +000 0001 = +1,1000 0001 的真值 = -000 0001 = -1
2. 原碼、反碼和補碼的介紹
2.1 原碼
原碼就是符號位加上真值的絕對值,即用第一位表示符號,其余位表示值,比如 8 位二進制:
[+1]原碼 = 0000 0001
[-1]原碼 = 1000 0001
因為第一位是符號位,所以8位二進制數的取值范圍就是:[1111 1111 , 0111 1111],即:[-127 , 127] 。
原碼是人腦最容易理解和計算的表示方式。
2.2 反碼
反碼的表示方法是:正數的反碼是其本身;而負數的反碼是在其原碼的基礎上,符號位不變,其余各個位取反。
[+1] = [00000001] 原 = [00000001] 反
[-1] = [10000001] 原 = [11111110] 反
可見如果一個反碼表示的是負數,人腦無法直觀的看出來它的數值,通常要將其轉換成原碼再計算。
2.3 補碼
補碼的表示方法是:正數的補碼就是其本身;負數的補碼是在其原碼的基礎上,符號位不變,其余各位取反,最后 +1(即在反碼的基礎上 +1)。
[+1] = [00000001] 原 = [00000001] 反 = [00000001] 補
[-1] = [10000001] 原 = [11111110] 反 = [11111111] 補
對於負數,補碼表示方式也是人腦無法直觀看出其數值的。通常也需要轉換成原碼在計算其數值。
3. 原碼、反碼和補碼的作用
現在我們知道了計算機可以有三種編碼方式表示一個數。對於正數,三種編碼方式的結果都相同。例:
[+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 = 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 則不存在了。
使用補碼,不僅僅修復了 0 的符號以及存在兩個編碼的問題,而且還能夠多表示一個最低數。
(-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]原,這是不正確的)。
這就是為什么8位二進制,使用原碼或反碼表示的范圍為 [-127, +127],而使用補碼表示的范圍是 [-128, 127]。
因為機器使用補碼,所以對於編程中常用到的 32 位 int 類型,可以表示范圍是 [-2^31,2^31-1],因為第一位表示的是符號位,使用補碼表示時又可以多保存一個最小值。
4. 進制轉換
4.1 十進制轉二進制
十進制整數轉二進制整數
十進制整數轉換為二進制整數采用“除 2 反向取余”法。具體做法是:使用“短除法”,用 2 整除十進制整數,可以得到一個商和余數;再用 2 去除商,又會得到一個商和余數;如此循環進行,直到商為 0 時為止,然后從下向上讀取每一次的余數。
如:將 789 轉換為二進制
789 / 2 = 394 …… 1
394 / 2 = 197 …… 0
197 / 2 = 98 …… 1
98 / 2 = 49 …… 0
49 / 2 = 24 …… 1
24 / 2 = 12 …… 0
12 / 2 = 6 …… 0
6 / 2 = 3 …… 0
3 / 2 = 1 …… 1
1 / 2 = 0 …… 1
從下向上讀取每一次的余數,把它們連接為字符串,就是答案:789 = 1100010101(B)
十進制小數轉二進制小數
十進制小數轉換成二進制小數采用“乘 2 取整,順序排列”法。具體做法是:用 2 乘十進制小數,可以得到積,將積的整數部分取出,再用 2 乘余下的小數部分,又得到一個積,再將積的整數部分取出,如此進行,直到積中的小數部分為零,此時 0 或 1 為二進制的最后一位。或者達到所要求的精度為止。
然后把取出的整數部分按順序排列起來,先取的整數作為二進制小數的高位有效位,后取的整數作為低位有效位。
程序實現
1 # 讀取一個十進制數字 2 while 1: 3 try: 4 num = float(input("請輸入一個十進制的數字:")) 5 break 6 except: 7 continue 8 9 # 整數部分的計算 10 int_result = 0 11 # 如果整數部分為0,無需繼續計算 12 if int(num) == 0: 13 pass 14 else: 15 int_part = int(str(num)[:str(num).find(".")]) 16 # 用於存儲每一次計算的余數 17 int_result = [] 18 # 開始循環除以2 19 while int_part > 0: 20 int_part, remainder = divmod(int_part, 2) # 獲取商和余數 21 int_result.append(remainder) 22 int_result = int("".join(list(map(str, int_result[::-1])))) 23 24 # 小數部分的計算 25 float_part = float(str(num)[str(num).find("."):]) 26 float_result = 0 27 # 如果小數部分為0,無需繼續計算 28 if float_part == 0: 29 # 最終結果=整數部分+小數部分 30 final_result = int_result 31 else: 32 # 用於存儲每一次計算的整數部分 33 float_result = [] 34 while 1: 35 tmp_result = float_part * 2 36 # 取出整數部分(0或1) 37 float_result.append(int(tmp_result)) 38 if tmp_result == int(tmp_result): 39 break 40 float_part = float("0."+str(tmp_result)[str(tmp_result).find(".")+1:]) 41 float_result = float("0."+"".join(list(map(str, float_result)))) 42 43 # 最終結果為整數部分+小數部分 44 print(int_result+float_result)
4.2 二進制轉十進制
轉換方法
小數點前或者整數要從右到左用二進制的每個數去乘以 2 的相應次方並遞增,小數點后則是從左往右乘以二的相應負次方並遞減。
程序實現
1 # 讀取一個二進制數字 2 while 1: 3 try: 4 bin_num = float(input("請輸入二進制數字:")) 5 break 6 except: 7 continue 8 # 整數部分 9 int_bin = str(bin_num)[:str(bin_num).find(".")][::-1] 10 result = 0 11 for i in range(len(int_bin)): 12 if int_bin[i] == "1": 13 result += 2**(i) 14 # 小數部分 15 float_bin = str(bin_num)[str(bin_num).find(".")+1:] 16 for i in range(len(float_bin)): 17 if float_bin[i] == "1": 18 result += 2**(-(i+1)) 19 # 最終結果=整數部分+小數部分 20 print(result)
4.3 內置函數進行進制轉換
1 # 十進制轉其他進制 2 hex(n) # 10進制的n轉16進制 3 oct(n) # 10進制的n轉8進制 4 bin(n) # 10進制的n轉2進制 5 6 # 其他進制轉十進制 7 int("16", base=16) # 將16進制的16 轉成10進制 8 int("7", base=8) # 將8進制的7 轉成10進制 9 int("1", base=2) # 將2進制的1 轉成10進制