這是我查到一些資料後的理解,希望能多一點對整數溢位後為何會正負循環的了解。
數字系統
首先要了解的是數字系統,因為一般電腦是採用二進制 (Binary) 來作數字計算,而不是我們習慣的十進制 (Decimal)。
我們一般人都是用十進制,十進制就是用 0、1、2、3、4、5、6、7、8、9
十個數字來表達,一個位數就有十種狀態,從 0 開始往上累積,超過 9 就進一位。
而二進制是什麼呢?二進制就是用 0、1
兩個數字來表達,一個位數就只有兩種狀態表達,從 0 開始往上累積,超過 1 就進一位。
以十進制來說,13
這個數字其實應該寫成 1*10^1 + 3*10^0
這樣。10^0
是 1
,也就是代表個位數,乘以幾就是這個位數累積到幾,超過 9
就進一位到 10^1
這一位,也就是十位數;再累積超過 9
,就進位到 10^2
百位數,以此類推。
那二進制怎麼表達呢?例如有一個二進制的數字長這樣 1101
,如同十進制,我們可以表達成 1*2^3 + 1*2^2 + 0*2^1 + 1*2^0
。這樣公式算完的答案其實就是十進制的 13
,大家可以算一下試試看。其實其他如八進制、十六進制也是相同原理,只不過超過十進制的進制系統,因為阿拉伯數字無法在單一位數表達 10
以上的數字大小,所以會配合英文字母 A、B、C、D
等等。
另外複習一下國小(國中?)數學,小數點的部分,以我們常用的十進制來說,小數點後第一位是 10^-1
、第二位是 10^-2
,以此類推;相對的二進制也是一樣規則,小數點後第一位是 2^-1
、第二位是 2^-2
這樣。
電腦基本運算方式
前面說到一般電腦是用二進制運算,這是什麼意思?
舉個例子,今天假設一台電腦,我們拉了第一條電線在裡面,一條電線怎麼表達不同的狀態呢?對,就是通電,有通電是一種狀態,沒通電是一種狀態。當然比較實際的作法是透過電壓,高電壓是一種狀態,低電壓是一種狀態,這樣來表達兩種狀態。
因此一條電線就能表達兩種狀態,剛好符合二進制只用 0、1
兩個數字來表達所有數字的狀態,而這樣能表達兩種狀態(1 或 0)的一條電線就是 1 bit。順便講一下因為某些原因,8 bits 被規定為 1 byte。bit 就是我們常聽到的位元,byte 就是我們更常聽到的位元組。
那 1 bit 可以表達兩種狀態,以二進制來說就是可以表達 0 跟 1;那 2 bits 呢?是四種;那 3 bits 呢?是六種?錯,是八種。
我們用列舉的方式說明,3 bits 意思是有三條電線各自有 0 跟 1 兩種狀態,因此我們可以列出有下列八種狀況:
狀況 | 第一條電線 | 第二條電線 | 第三條電線 |
---|---|---|---|
一 | 0 | 0 | 0 |
二 | 0 | 0 | 1 |
三 | 0 | 1 | 0 |
四 | 0 | 1 | 1 |
五 | 1 | 0 | 0 |
六 | 1 | 0 | 1 |
七 | 1 | 1 | 0 |
八 | 1 | 1 | 1 |
簡略來說就是其實是有 2*2*2=8,也就是 23 種狀況;以此類推,n bit(n 條電線)就有 2n 種狀況。
那 1 byte 可以表達幾種狀況?
前面說過 1 byte = 8 bit,所以 1 byte 就可以表達 28 = 256 種狀況。
前面也講過二進制怎麼換算十進制,也就代表了二進制的每一種狀況都對應到一個十進制的數字,至於對應到的是哪一個,就是用前面教過的換算方法換算。
因此,1 byte 可以表達 256 種狀況,每一種狀況剛好表達了一個二進制數字;再以二進制對應十進制來考慮,也就是 1 byte 可以表達從 0 開始,最多到 255 的各種十進制的正整數數字。
資料型態
到這邊可能就會有人問,那負數呢?
沒錯,講到這邊我們都只處理了正整數(包括 0),還沒講到負整數。講負整數之前,順便先講一下資料型態。
一般電腦要存這些數字,也是以這樣 1 個 byte、1 個 byte 的存,就想像記憶體或硬碟裡面就是一大堆小格子,每個小格子裡面通常就是 8 條線,也就是 8 bits,因此一個小格子就是 1 byte,也就是一個小格子就能存 256 種不同的狀態,以正整數來說,就是能存 0-255 這個範圍內的任何一個數字。
但 0-255 這樣的範圍很顯然太小了啊,儲存喝一杯可樂、吃一頓麥當勞的價格可能還可以,但要是我去麥當勞想點個六塊炸雞餐台幣 288 元怎麼辦?
對,就只好用兩個小格子 2 bytes 來存啊,兩個 256 加一起總可以了吧?
這句話只對了一半。
的確是要用 2 bytes 來存,但請記住 2 bytes 是有 28*28 = 216 = 65,536 種狀況,而不是 256 + 256 = 512 種狀況唷!就跟 2 bits = 2*2 種狀況一樣,2 bytes 也是 28*28 種狀況唷!至於有指數的數字相乘則指數相加這件事,請翻數學課本或上網谷哥。
接著,我們已經知道 2 bytes 有 65,536 種狀態,以正整數來說就是 2 bytes 就可以存 0-65535 這個範圍內任一整數,但某些狀況下,這個數字可能也不夠大怎麼辦?
就只好多用幾個 byte 啊,至於要用幾個 byte 才夠,大家的意見就不太一樣了。
舉例來說,在 Java 裡面,對於整數就有分成 byte
、short
、int
、long
四種資料型態,名稱不重要,重要的是 Java 規定 byte
型態整數佔 1 byte、short
佔 2 bytes、int
佔 4 bytes、long
佔 8 bytes。
整數儲存方式
以 byte
這個整數型態來舉例,儲存 1
這個正整數就是先轉成二進制,也就是 0000 0001
來存進 byte
的 8 bytes 空間,大概可以表達成這樣:
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
八個位元,剛好一個位元存一個二進位數字。
接著就可以來考慮負整數該怎麼辦了。
一般來說,不論是 byte
、short
、int
還是 long
,都是用最左邊的那一個位元來儲存正負號,0
是正號,1
是負號。所以如果是負整數 -1
就要表達成這樣:
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
1’s 補數
但是,這樣表達會有另外一個問題。
用最左邊的位元來儲存正負號,所以實際上八位元的空間只有七位元的空間能用來儲存數字,也就是只表示 215 種狀況,加上我們現在已經考慮到負整數,所以理論上範圍就會變成 -215~215 這個範圍。
這邊就產生一個問題,照前面的邏輯,0
換成八位元的二進制應該表達為 0000 0000
,但是在第一個位元用來表示正負號的情況下,那豈不是會出現 0000 0000
代表 +0
以及 1000 0000
代表 -0
這種好像會有兩種 0
的奇怪的狀況??
有人想到解決的辦法,就是取補數。
補數是什麼呢?
我覺得比較簡單的講法就是兩個加起來會剛好開始進位的數字,互相之間就是補數,譬如在十進制中 1
跟 9
互為補數、23
跟 77
互為補數這樣。
但是電腦是二進制啊,二進制補數怎麼算?二進制補數最簡單了,就是把 0
跟 1
通通反過來就好,譬如 1001
翻成 0110
就變成補數啦,不信可以自己加加看是不是剛好會進位。
然後我們就可以利用補數來表達負整數的部分,所以像是 0110
換算成十進制是 6
,而 -6
就利用補數法表達為 1001
,剛好連最左邊正負號的欄位都同時翻轉成正確的正負號,是不是很方便呢?
所以取 1’s 補數後,以四位元來表達的整數組合就會變成如下:
一般十進制正整數 | 對應二進制 | 換算十進制整數 | 1’s補數法 |
---|---|---|---|
0 | 0000 | +0 | +0 |
1 | 0001 | +1 | +1 |
2 | 0010 | +2 | +2 |
3 | 0011 | +3 | +3 |
4 | 0100 | +4 | +4 |
5 | 0101 | +5 | +5 |
6 | 0110 | +6 | +6 |
7 | 0111 | +7 | +7 |
8 | 1000 | -0 | -7 |
9 | 1001 | -1 | -6 |
10 | 1010 | -2 | -5 |
11 | 1011 | -3 | -4 |
12 | 1100 | -4 | -3 |
13 | 1101 | -5 | -2 |
14 | 1110 | -6 | -1 |
15 | 1111 | -7 | -0 |
這個方法就被稱作 1’s 補數法,但要注意,是對二進制取補數來表達十進制的負整數,而不是對十進制取補數。
2’s 補數
但是仔細想想,這樣還是沒有解決會同時有 +0
跟 -0
,的問題啊,只是原本 -0
表達成 1000
,現在 -0
變成用 1111
來表達而已啊?
所以為了徹底解決這個問題,於是就有了所謂的 2’s 補數法來彌補這個問題。
2’s 補數法就是針對 1’s 補數法會從 -0
開始排列而做的調整,原理就是利用 1’s 補數法將正整數的二進制取補數變成負整數的二進制後,對非代表正負號的二進制部分加 1
來代表原本 1’s 補數法代表的十進制數字。
一樣用十進制的 6
來當例子,十進制的 6
換成二進制變成 0110
,而 -6
就是取 1’s 補數變成 1001
,然後對 1001
非代表正負號的二進制部分加 1
,也就是 001
+ 1
= 010
,再將原本代表正負號的第一位 1
加回去,所以 1010
才代表原本 1’s 補數所代表的 -6
。
在這樣的方式下,-0
會自然地消失,只剩下 +0
代表 0
,也因此負整數會多一個,在 4 bits 的情況下,2’s 補數法的負整數就會多出 -8
。
如果我們看四個位元的整數組合,可以表列如下,讓大家直觀明白 2’s 補數法的對應關係。
一般十進制正整數 | 對應二進制 | 1’s補數法 | 2’s補數法 |
---|---|---|---|
0 | 0000 | +0 | +0 |
1 | 0001 | +1 | +1 |
2 | 0010 | +2 | +2 |
3 | 0011 | +3 | +3 |
4 | 0100 | +4 | +4 |
5 | 0101 | +5 | +5 |
6 | 0110 | +6 | +6 |
7 | 0111 | +7 | +7 |
8 | 1000 | -7 | -8 |
9 | 1001 | -6 | -7 |
10 | 1010 | -5 | -6 |
11 | 1011 | -4 | -5 |
12 | 1100 | -3 | -4 |
13 | 1101 | -2 | -3 |
14 | 1110 | -1 | -2 |
15 | 1111 | -0 | -1 |
所以 2’s 補數法其實只是針對 1’s 補數法的負整數部分調整其對應的十進制數字。
整數溢位 Integer Overflow
終於要講到溢位了,yeah~
所謂整數溢位就是超過整數資料型態能儲存的範圍,例如剛才提的例子是用 4 bits 來儲存整數,所以有 24 也就是 16 種狀況,對應到利用 2’s 補數法調整過的十進制整數範圍就是 -2323-1,也就是 -8+7。
那如果我利用這樣 4 bits 的整數變數儲存正整數 7
,但是變數在參與運算的時候不小心又加了一怎麼辦?這就會超出這個整數變數能儲存的範圍,那實際這個變數裡面的整數會怎麼樣?會變成 -8
。
為什麼?我們來算算看。
+7
對應的二進制是 0111
,不小心再加了一會變多少?用二進制運算就是 1000
。然後讓我們看一下上面的表格,1000
對應的 2’s 補數法那格是多少?就是 -8
。
反過來說,-8
對應的二進制是 1000
,不小心又多減了一,1000
減一就變成 0111
,那 0111
對應的十進制數字是多少?就是 +7
。
其實整數溢位會正反循環的道理就在這邊,不知道這樣講我十年後看不看得懂……orz
Last modified on 2017-10-01