計算機數值編碼
計算機數值編碼是指電子計算機內部表示數字的方式[1]。通常數值會儲存為位元組序列,而以組成它們的位元數給以名稱,例如32位整數、64位浮點數。對於計算機處理器指令集所使用的位元編碼,通常需要將其格式再轉換以供外界的使用者來判讀,例如打印和顯示。對於紙筆書寫看似相同的文字符號或數值,在不同類型的電子計算機處理器內部中,可能有不同的約定用於整數和實數。大多數計算都是使用適合處理器暫存器的數碼格式來執行,但是某些軟件系統允許使用多種儲存方式,來表示任意大數。
二進制表示法
電腦會將資料用一串二進制的數字來表示,二進制數字是由位元所組成,也會將位元組合成較大的單位,像是位元組。
二進制數值 | 二進制數值 |
---|---|
000 | 0 |
001 | 1 |
010 | 2 |
011 | 3 |
100 | 4 |
101 | 5 |
110 | 6 |
111 | 7 |
二進制數字的長度(b) | 可能值的個數(N) |
---|---|
1 | 2 |
2 | 4 |
3 | 8 |
4 | 16 |
5 | 32 |
6 | 64 |
7 | 128 |
8 | 256 |
9 | 512 |
10 | 1024 |
... | |
計算機的基本儲存單位是「位元」(bit),通過開關變化設置表達值0或1。位元的概念可以表示為1或0、開或關、是或不是、真或假等等。
一個位元可以表示二個值,而幾個位元可以表示較大的數值。例如,三個位元就可以表示八個不同的數值,如表1所示。位元數越多,可以表達的數字也就越多,可以表達的數字會隨着位元數指數成長, n位數的二進制數字可以表達2n種可能的數字,如表2所示。
為了方便理解,會將特定個數的二進制數字組合起來,變成較大的單位,且有其名稱。
字節(byte)是長度可以表示字符的二進制位數組。在現有大部份的電腦中,字節是由8個位元所組成,由於字節的定義和表示字符所需要的位元個數有關,有些較舊的電腦可能有不同的字節定義[2]。在許多計算機系統結構中,字節是最小的可定址單元,也就是位址定址的最小單位。例如,即使64位元處理器一次可以處理64位元,仍然將其記憶體以8位元為單位來分割。這就是字節定址的記憶體。以往有許多CPU是以8位元的整數倍來讀取資料[3]。字節由8個位元所組成的情形非常常見,但不是標準化的定義,因此有出現八位元(octet)的名稱,明確說明是由8個位元所組成的單位。
nibble是由4個位元所組成的單位[4]。4個位元可以表示16個值,因此nibble也會稱為十六進制數字[5]。
八進制和十六進制數
對二進制數字的表示方法。計算機通常使用二進制來表達數據,但是在實際中如果使用像這樣的二進制:
- 1001 0011 0101 0001
這些很長的數字對於人手計算及觀看上會很容易出錯。因此計算機會使用八進制或更常使用的十六進制的,一個基於二進制的表達方式。
這個簡單的解決方法十分有效,在平常的十進制體系中,有10數字(0到9)按以下方式構成排列:
- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ...
在八進制中,只有八個數字(0到7)按以下方式構成排列:
- 0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 ...
也即是說,八進制的「10」相當於十進制的「8」,八進制的「20」相當於十進制的「16」,以此類推。
在十六進制中,只有十六個數字(0到9,然後是從a到f)按以下方式構成排列:
- 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 ...
也即是說,十六進制的「10」相當於十進制的「16」,十六進制的「20」相當於十進制的「32」。
這些表示數值的方法都是表位置的系統,但是它們使用的不是像十進制那樣的10,而是分別使用8和16。例如:
- 八進制756
- = 7 × 82 + 5 × 81 + 6 × 80
- = 7 × 64 + 5 × 8 + 6 × 1
- = 448 + 40 + 6 = 十進制494
- 十六進制3b2
- = 3 × 162 + 11 × 161 + 2 × 160
- = 3 × 256 + 11 × 16 + 2 × 1
- = 768 + 176 + 2 = 十進制946
於八進制來說,他們剛好和3位元二進制有一個完美的對應關係:
- 000 = 八進制0
- 001 = 八進制1
- 010 = 八進制2
- 011 = 八進制3
- 100 = 八進制4
- 101 = 八進制5
- 110 = 八進制6
- 111 = 八進制7
相似地,一個十六進制數剛好和一個4位元的二進制數對應。
0000 = 十六进制0 1000 = 十六进制8 0001 = 十六进制1 1001 = 十六进制9 0010 = 十六进制2 1010 = 十六进制a 0011 = 十六进制3 1011 = 十六进制b 0100 = 十六进制4 1100 = 十六进制c 0101 = 十六进制5 1101 = 十六进制d 0110 = 十六进制6 1110 = 十六进制e 0111 = 十六进制7 1111 = 十六进制f
因此,把一個很長的二進制數轉換成一個八進制就變得很簡便,例如:把二進制1001001101010001轉化成八進制:
1 001 001 101 010 001 二进制 = 111521 八进制
轉換成十六進制則是:
1001 0011 0101 0001 = 9351 十六进制
因爲要把它轉換成十進制(37,713)比較複雜,八進制和十六進制使得轉換二進制機器級的數字變得簡單方便。
有符號整數和補碼
在定義負數中,有兩種普遍的方法,分別是「無符號整數」及「有符號整數」。
無符號整數
這個方法保留了一個位來表示數值的符號。這個「符號位」可以位於數值的最左邊,也可以位於數值的最右邊。如果這個符號位為0,表示數值是正的,如果這個符號位為1,表示數值是負數。然而這樣做對於計算機來說有可能帶來一些缺點。這種編碼使得0可以有正負兩種,有符號整數會浪費掉一個表達數值的範圍
有符號整數
更優勝的表達方式是對給定的位數的二進制數按其範圍分成兩半,其中前一半用來表示負數。例如,在4位數值中,你可以得到:
0000 = 十进制0 0001 = 十进制1 0010 = 十进制2 0011 = 十进制3 0100 = 十进制4 0101 = 十进制5 0110 = 十进制6 0111 = 十进制7 1000 = 十进制-8 1001 = 十进制-7 1010 = 十进制-6 1011 = 十进制-5 1100 = 十进制-4 1101 = 十进制-3 1110 = 十进制-2 1111 = 十进制-1
這個有符號整數的系統使用所知道的「補碼」編碼方式。
對16位元有符號數字編碼來說,可以為從-32,768到32,767的數編碼。
對一個32位元的有符號編碼系統來說,可以為從-2,147,483,648到2,147,483,647的數編碼。
與只改變符號位來表示負數的編碼方式相比,「補碼」編碼方式與之有所不同。例如對於-5來說,只對符號位編碼,應該是:
1101
但是對於「補碼」編碼方式來說,則是:
1011
這對於符號編碼來說是-3。關於為什麼計算機要使用補碼這種編碼方式會在後面解釋。
所以,可以以二進制方式來表示正負兩種不同的數值。請記住對於一個二進制數來說,只有兩種解釋方式。如果在內存中有一個這樣的二進制數值:
1101
-- 這只能解釋為十進制的「13」或「-5」。
定點小數
這種格式通常被用於商業計算(例如在電子表格或COBOL中);因為在這裏,丟棄小數位來記錄金錢是不能接受的。因此了解二進制如何存貯小數是十分有用的。
首先必須決定要用多少位元來存貯小數部分和多少位元來存儲整數部分。假設使用32位元來表示這種格式,那麼用16位元表示整數部分,16位元來表示小數部分。
小數部分可以沿用表示整數的方式:如果8位元接下來是4位元,是2位元,1位元,接下來就是半位元,1/4位元和1/8位元等等。
例如:
整数位 小数位 0.5 = 1/2 = 00000000 00000000.10000000 00000000 1.25 = 1 1/4 = 00000000 00000001.01000000 00000000 7.375 = 7 3/8 = 00000000 00000111.01100000 00000000
如果要表達1/5(十進制的0.2),那麼不能得到精確的數值表達方式。最好的方法只能是:
- 13107/65536 = 00000000 00000000.00110011 00110011 = 0.1999969... 十進制
- 13108/65536 = 00000000 00000000.00110011 00110100 = 0.2000122... 十進制
一些小數使用二進制的方式不能精確地表達出來。除非使用一個特殊的辦法。這個特殊的辦法是分別使用兩個數字來表達小數:一個是分子,一個是分母。然後可以使用學校學習的加、減、乘、除來得到它們。然而,這些方法不能表達更高級的數字(例如平方根),或者如果這兩個分母的最小公倍數很大的話,那就難以使用。這就是使用定點小數表達小數的缺點。
浮點小數
當使用了有符號和無符號的數值表達方式時。如果遇到連32位元也不足以表達的大範圍的數,或也許可以表達,但必須為此放棄小數位時,可以選擇的以獲得更大範圍的數值的表達方式的方法是使用「浮點小數」格式而拋棄「定點小數」格式。
在十進制中,對以下的表達方式很熟悉:
- 1.1030402 × 105 = 1.1030402 × 100000 = 110304.02
或簡寫成:
1.1030402E5
這表示「1.103402乘以一個1後面跟着5個零的數」。可以得到一個確定的稱謂「尾數」的數值(1.1030402),乘以一個10的某個冪級數(E5,表示105或100,000),也就是「冪級數」。如果使用一個負的冪級數,那就意味着乘以該正級數的倒數。例如:
- 2.3434E-6 = 2.3434 × 10-6 = 2.3434 × 0.000001 = 0.0000023434
使用這種定義的好處是可以得到更大範圍的數值,雖然尾數部分的精確度受到影響。相似的原理可以應用於二進制中為計算機使用。人們設計了很多方式,但是作常用的是由美國電器和電子工程師協會定義的一種方法。它對64位浮點格式的定義是:
- 11位元二進制表示指數,使用「超1023」格式。這種格式使得指數可以表示為0到2047的無符號數,但是在得到真實值前必須先減去1023;
- 52位元尾數,使用無符號數字表示;
- 一個符號位;
下面使用一個例子來了解計算機如何使用內存的8位元組存儲這些數字:
第0字节: S x10 x9 x8 x7 x6 x5 x4 第1字节: x3 x2 x1 x0 m51 m50 m49 m48 第2字节: m47 m46 m45 m44 m43 m42 m41 m40 第3字节: m39 m38 m37 m36 m35 m34 m33 m32 第4字节: m31 m30 m29 m28 m27 m26 m25 m24 第5字节: m23 m22 m21 m20 m19 m18 m17 m16 第6字节: m15 m14 m13 m12 m11 m10 m9 m8 第7字节: m7 m6 m5 m4 m3 m2 m1 m0
其中的「S」表示符號位,「X」表示指數位(階碼?),「m」表示尾數位。一旦這些數字被讀取,它將轉換成:
- <符號> × (1 + <小數形式的尾數>) × 2<階碼> - 1023
這個方式提供的數字範圍為:
最大值 | 最小值 | |
---|---|---|
正數 | 1.797693134862231E+308 | 4.940656458412465E-324 |
負數 | -4.940656458412465E-324 | -1.797693134862231E+308 |
這種方式也定義了一些不是數字的值,例如「NaNs」(「不是一個數字」)。這通常用來返回表示數字溢出的信息。一些程序使用32位元浮點小數。最普遍的是使用32位元尾數,1位元符號位,8位元階碼和「超127」格式。它提供7位十進制數字。
第0字节: S x7 x6 x5 x4 x3 x2 x1 第1字节: x0 m22 m21 m20 m19 m18 m17 m16 第2字节: m15 m14 m13 m12 m11 m10 m9 m8 第3字节: m7 m6 m5 m4 m3 m2 m1 m0
數字轉換時使用:
- <符號> × (1 + <小數形式的尾數>) × 2<階碼> - 127
它的範圍為:
最大值 | 最小值 | |
---|---|---|
正數 | 3.402823E+38 | 2.802597E-45 |
負數 | -2.802597E-45 | -3.402823E+38 |
通常可以把32位元的二進制浮點數稱為「單精度」浮點數,而把64位元的二進制浮點數稱為「雙精度」浮點數。如果使用real的時候,通常表示雙精度的浮點數;而使用float的時候,通常指單精度的浮點數。
但是要記住的是,浮點數在計算機的存儲是連續的:當計算機內存中有一個64位元數據時,它可能是一個雙精度的浮點數,也可能是兩個單精度的浮點數,或4個有符號或無符號的整數,或其它8位的數據,這取決於計算機如何讀取它們。如果計算機把4個無符號整數以雙精度浮點小數方式讀出的話,它可以得到一個雙精度浮點小數,但這個數據可能是一個垃圾數據。
所以,現在雖然已經解決了正、負數的存貯方式,但是對於浮點數來說,仍然存在一些與整數一樣的缺陷,例如:
- 像整數一樣,浮點數也有範圍。雖然得到了比整數要大得多的範圍,但是仍然是有限的,如果試圖把兩個很大的數乘起來,可能會得到「數據上溢」的錯誤。而如果把一個小的數字除以一個函大的數字,那就可能使得指數的數值出錯,出現「數據下溢」的錯誤。通常把最大值稱為「機器無窮」,因為它是計算機所能處理的最大的數字。
- 另一個問題是精度。雖然有15位元的數字來表示很大的數,但是當對它進行四則運算的時候,它們可能不給任何提示就把一些數字丟棄。這意味着,如果把一個很小的數加到一個很大的數值上去的時候,由於這個數字太小,以至於15位元或16位元的精度都不能顯示它,計算機就會把這個數字丟棄。如果在做計算的時候得到一個十分奇怪的數字,可能需要檢查數據範圍是否合適。
- 這意味着,如果做浮點計算,較小的數字很可能被丟棄了。雖然這在平常來說並不明顯,但是如果在做要求很高的數學分析工作,這些錯誤可能會累積起來,以至於最後得到的結果十分不準確。
- 這個錯誤對進行數學研究的人來說十分重要,他們必須對誤差十分了解,以及研究一些辦法來減少誤差,並且應該可以估計到誤差的大小。
- 順便說一句,「精度」問題與「範圍」問題不同,前者指的是有關尾數的表示範圍,後者指的是指數的表達範圍。
- 另外一個不太明顯的誤差是由於浮點數的二進制和十進制並不完全相等。如果操作的數好是2的冪級數的倒數,例如0.75,那是用二進制可以準確的標示為0.11,因為它剛好是1/2+1/4的值。可是不幸的是,可能通常不會得到如此恰到好處的數字,這就是說,計算機會把一些數字丟掉,例如要表示0.1,只能使用無窮循環的二進制小數0.000110011……表示。
計算機不是萬能的,它只是一部機器,並要符合一定的規則和受到一定的限制。在計算機雖好的解決方法下也有一些不可避免的不精確。
程式語言中的數
對於低級語言的編程者來說,他們要擔心有符號和無符號、定點和浮點數的運算。他們必須使用十分不同的代碼來實現操作。
但是,對高級語言的編程者來說,諸如LISP和Python提供了一些列諸如「有理數」、「複數」之類的抽象數據類型。而他們可以斷言他們的系統可以使用數學操作做正確的運算。由於操作符重載,數學運算可以應用於任何數字——無論是有符號的、無符號的、有理數、定點小數、浮點小數或複數。
字符編碼:ASCII和字符串
對於字符,例如姓名、地址或寫給朋友的信,如果還記得幾位是幾位的話,當然可以使用數字來表達字符「A」或「?」或「Z」。計算機同理。計算機每次處理一個字節,所以使用單字節的數據來表達單個字符會很方便。可以使用這個:
0100 0110 (hex 46)
來表示字符「F」。計算機使用這樣的「字符編碼」來向顯示程序傳送要求的文本。
下面是一個用來存儲西方字母的標準二進制編碼,稱爲「美國信息交換標準碼」(英文簡稱「ASCII」),下面的編碼為ASCII編碼,使用「d」表示十進制(decimal)編碼,「h」表示十六進制(hexadecimal)代碼,「o」表示八進制(octal)代碼:
ASCII码表 ______________________________________________________________________ ch ctl d h o ch d h o ch d h o ch d h o ______________________________________________________________________ NUL ^@ 0 0 0 sp 32 20 40 @ 64 40 100 ' 96 60 140 SOH ^A 1 1 1 ! 33 21 41 A 65 41 101 a 97 61 141 STX ^B 2 2 2 " 34 22 42 B 66 42 102 b 98 62 142 ETX ^C 3 3 3 # 35 23 43 C 67 43 103 c 99 63 143 EOT ^D 4 4 4 $ 36 24 44 D 68 44 104 d 100 64 144 ENQ ^E 5 5 5 % 37 25 45 E 69 45 105 e 101 65 145 ACK ^F 6 6 6 & 38 26 46 F 70 46 106 f 102 66 146 BEL ^G 7 7 7 ` 39 27 47 G 71 47 107 g 103 67 147 BS ^H 8 8 10 ( 40 28 50 H 72 48 110 h 104 68 150 HT ^I 9 9 11 ) 41 29 51 I 73 49 111 i 105 69 151 LF ^J 10 a 12 * 42 2a 52 J 74 4a 112 j 106 6a 152 VT ^K 11 b 13 _ 43 2b 53 K 75 4b 113 k 107 6b 153 FF ^L 12 c 14 , 44 2c 54 L 76 4c 114 l 108 6c 154 CR ^M 13 d 15 _ 45 2d 55 M 77 4d 115 m 109 6d 155 SO ^N 14 e 16 . 46 2e 56 N 78 4e 116 n 110 6e 156 SI ^O 15 f 17 / 47 2f 57 O 79 4f 117 o 111 6f 157 DLE ^P 16 10 20 0 48 30 60 P 80 50 120 p 112 70 160 DC1 ^Q 17 11 21 1 49 31 61 Q 81 51 121 q 113 71 161 DC2 ^R 18 12 22 2 50 32 62 R 82 52 122 r 114 72 162 DC3 ^S 19 13 23 3 51 33 63 S 83 53 123 s 115 73 163 DC4 ^T 20 14 24 4 52 34 64 T 84 54 124 t 116 74 164 NAK ^U 21 15 25 5 53 35 65 U 85 55 125 u 117 75 165 SYN ^V 22 16 26 6 54 36 66 V 86 56 126 v 118 76 166 ETB ^W 23 17 27 7 55 37 67 W 87 57 127 w 119 77 167 CAN ^X 24 18 30 8 56 38 70 X 88 58 130 x 120 78 170 EM ^Y 25 19 31 9 57 39 71 Y 89 59 131 y 121 79 171 SUB ^Z 26 1a 32 : 58 3a 72 Z 90 5a 132 z 122 7a 172 ESC ^[ 27 1b 33 ; 59 3b 73 [ 91 5b 133 { 123 7b 173 FS ^\ 28 1c 34 < 60 3c 74 \ 92 5c 134 124 7c 174 GS ^] 29 1d 35 = 61 3d 75 ] 93 5d 135 } 125 7d 175 RS ^^ 30 1e 36 > 62 3e 76 ^ 94 5e 136 ~ 126 7e 176 US ^_ 31 1f 37 ? 63 3f 77 _ 95 5f 137 DEL 127 7f 177 ______________________________________________________________________
上面這個列表的最左邊有一個字符,例如「FF」和「BS」,這些都不是文本字符。相反,它們是控制字符,當這些字符發送到特定的設備時,它將產生一些動作。例如「FF」表示換頁,或彈出;「BS」表示退格,而「BEL」表示一個響聲。在一個文本編輯器中,它們會顯示成一個白色或黑色的方塊,或笑臉、音符或其它替換符。要打出這些字符,可以使用CTRL鍵和一個合適的代碼。例如同時按住「CTRL」和「G」,或簡寫成「CTRL-G」或「^G」可以打出一個BEL字符。
上面這個ASCII碼表示定義了128個字符,這意味着ASCII碼只需要7位元。但是,很多計算機都以字節為單位存儲信息。這個額外的一位元在某些英語以外的國家被用於二個128個字集,一個「擴展」字集,例如EASCII
除了EASCII,還有很多不同的「擴展」字集,提供很多例如數學符號等的符號或非英語字符。這個擴展字集並沒有進行標準化,並經常會引起混淆,同樣的字串在不同的裝置上顯示不同的內容
這個表格強調了位元就是位元。這樣的話,可以使用位來表示字符。可以把特殊的代碼描述成特殊的十進制、八進制和十六進制,但是它們仍然是相同的代碼。這些數值的表達,無論是十進制、八進制或十六進制,都只是相同的位的表達。
在一段話中表達很多的字符,例如:
- Tiger, tiger burning bright!
以ASCII碼,會表示成:
- 54 69 67 65 72 2c 20 74 69 67 65 72 20 62 75 ...
計算機把這種ASCII「字符串」以連續空間的「數組」來存儲。一些應用程式可以包括一個二進制數值表示字符串的長度,但是更通常的做法是使用一個表示結尾的字符NULL(ASCII表中的0字符〕表示字符串的結束。
參見
參考資料
- ^ Jon Stokes. Inside the machine: an illustrated introduction to microprocessors and computer architecture. No Starch Press. 2007: 66. ISBN 978-1-59327-104-6.
- ^ byte definition. [24 April 2012]. (原始內容存檔於2017-04-03).
- ^ Microprocessor and CPU (Central Processing Unit). Network Dictionary. [1 May 2012]. (原始內容存檔於3 October 2017).
- ^ nybble definition. [3 May 2012]. (原始內容存檔於2016-10-11).
- ^ Nybble. TechTerms.com. [3 May 2012]. (原始內容存檔於2019-03-13).