# 淺談計算機編碼(整數篇) ###### tags: basic > 計算機中對於數字不管是十進位、二進位、十六進位、整數、浮點數等等的表達方式或運算操作,因為計算機本身的限制,都會跟人類世界直覺想像的有點不同,常常會因為某些觀念上的不齊全,直接用我們直覺的方式去撰寫就會有些無法預期的漏洞,所以熟悉跟將這些編碼化為自己的習慣是相當重要的。 > > 這篇的撰寫是根據 CS:APP 這本書的第二章作為基礎,算是作為自己讀後彙整的筆記,並會搭配些許實驗。 ## 實驗環境 ```shell $ uname -a Linux noah-UX330UA 4.15.0-43-generic #46~16.04.1-Ubuntu SMP Fri Dec 7 13:31:08 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux $ gcc -version gcc (Ubuntu 5.5.0-12ubuntu1~16.04) 5.5.0 20171010 ``` ### 16 進位表示 在計算機中最小的位址紀錄單元為 8 個 byte,而如果用二進位表示會太長,用十進位轉換成二進位又會太麻煩,所以都是用十六位元來表示位址。 在 C 語言中用 `0xFFFFFFFF` 的格式來表示記憶體位置,並且可以用`printf("%p", 變數);` 來印出記憶體表示之格式。 以下是三種進位的轉換表。 | 十六進位 |0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ------- | - | - | - | - | - | - | - | - | - | 十進位 | 0 | 1| 2 | 3 | 4| 5 | 6 | 7 | 8 | 二進位 | 0000| 0001| 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 十六進位 |9 | A | B | C | D | E | F | | ------- | - | - | - | - | - | - | - | | 十進位 | 9 | 10| 11 | 12 | 13| 14 | 15 | | 二進位 | 1001| 1010| 1011 | 1100 | 1101 | 1110 | 1111 ### byte 表示順序 byte 的表示順序根據計算機的不同分成兩種 `little endian` 和 `big endian` (大部分的機器都是以 `little endian` 作為表示,而網路傳輸使用的都是 `big endian`)。 * `little endian` 在 `little endian` 中,記憶體位置最低位數(最右)會放在記憶體的前面,例如 `0x01234567`。 | 0x100 | 0x101 | 0x102 | 0x103 | | -------- | -------- | -------- |---- | 67 | 45 | 23 | 01 | * `big endian` 在 `big endian` 中,跟 `little endian` 相反,最高位數放在記憶體最前面。 | 0x100 | 0x101 | 0x102 | 0x103 | | -------- | -------- | -------- |---- | 01 | 23 | 45 | 67 | * [Big-Endian 與Little-Endian 的差異與判斷程式碼- G. T. Wang](https://blog.gtwang.org/programming/difference-between-big-endian-and-little-endian-implementation-in-c/) 可以利用這個連結給的小程式來判斷自己電腦的表示式是哪種。 ## 整數類型 在 C 語言中有許多不同的整數的類型,而這些類型在不同 bits 的機器上會有不一樣的最大值與最小值,下面的列表是在 32 位元跟 64 位元機器上值的範圍。 #### 32位元 | 整數類型 | 最小值 | 最大值 | | ------------- | -------- | ------------ | | [signed] char | -128 | 127 | | unsigned char | 0 | 255 | | short | -32768 | 32767 | | unsigned short | 0 | 65535 | | int | -2147483648 | 2147483647 | | unsigned | 0 | 4294967295 | | long | -2147483648 | 2147483647 | | unsigned long | 0 | 4294967295 | | int32_t | -2147483648 | 2147483647 | | uint32_t | 0 | 4294967295 | | int64_t | -9223372036854775808 | 9223372036854775807| | uint64_t | 0 | 18446744073709551615 | #### 64位元 | 整數類型 | 最小值 | 最大值 | | ------------- | -------- | ------------ | | [signed] char | -128 | 127 | | unsigned char | 0 | 255 | | short | -32768 | 32767 | | unsigned short | 0 | 65535 | | int | -2147483648 | 2147483647 | | unsigned | 0 | 4294967295 | | long |-9223372036854775808 | 9223372036854775807 | | unsigned long | 0 | 18446744073709551615 | | int32_t | -2147483648 | 2147483647 | | uint32_t | 0 | 4294967295 | | int64_t | -9223372036854775808 | 9223372036854775807| | uint64_t | 0 | 18446744073709551615 | ## 整數表示 整數在計算中有兩種表示方式,一種是只能表示非負數,另外一種是能夠表示負數、零、正數,這兩種表示方式在編碼上都有些不同,其在不同位元的機器上也有不同,接下來就來介紹這兩種整數的編碼方法。 #### 無符號整數二進位編碼 $B2U_w(x) = \displaystyle\sum_{i=0}^{w-1}x_i2^i$ 上面的公式是作為將二進位轉成無符號整數的公式。 $B2U_w([1011]) = 1 *2^3 + 0 * 2^2 + 1 * 2^1 + 1 *2^0 = 11$ #### 補數編碼(有符號整數表示法 two's complement) 利用二進位數中的最高位數來表示,如果是 `1` 就為負,`0` 則為正 $B2U_w(x) = -x_{w-1}2^{w-1}+\displaystyle\sum_{i=0}^{w-2}x_i2^i$ $B2U_w([1011]) = -1 *2^3 + 0 * 2^2 + 1 * 2^1 + 1 *2^0 = -8$ $B2U_w([0111]) = 0 *2^3 + 1 * 2^2 + 1 * 2^1 + 1 *2^0 = 7$ ## 有號整數與無號整數之間轉換 > 所謂的有號和無號的轉換並不是在一般數學所表示的將 `-` 去掉就轉換成功,這兩個在編碼上就完全不同,無號是用二進位,有號是用 two's complement ,所能表示數字的範圍大小也不同,所以再做 `int -> unsigned` 或 `unsigned -> int` 時必須要意識到自己在幹麻,還有結果是否會是自己預期。 * **有號整數 -> 無號整數 轉換原理** * 當要從有號整數轉成無號整數時,如果有號整數大於零的時候,就直接轉換成無號整數。 * 如果為負數要轉換為無號整數時,$x + 2^w$ 利用這個公式來計算,$w$ 為數據類型 bits 長。 * 例如在 16 bits 的機器上,要將 $-12345$ 轉換成無號整數,套用上述公式... $-12345 + 2^{16} = 53191$ * 特別注意到因為這樣轉換的方法,將 -1 轉換成無號整數後會變成在該機器上的無號整數最大值... $-1 + 2^{16} = 65535$ * **無號整數 -> 有號整數 轉換原理** * 如果欲轉換的無號整數是小於有號整數的最大值(`參照整數類型的表格`),就直接轉換。 * 如果是大於有號整數的最大值,套用 $x-2^w$ 的公式,$x$ 為無號整數,$w$ 為機器的 bits 數。 * 例如在 16 bits 的機器上, 給一數 $32768$ 為有號整數最大值再加一,套用公式就為 $32768 - 2^{16} = -32768$ ,這樣轉換下來就會得到有號整數的最小值。 * 如果是給無號整數的最大值 $65535$ 代入公式,$65535 - 2^{16} = -1$ 跟 *有號整數 -> 無號整數* 成對射。  這張圖能清楚的表示兩種表示方式的轉換。 ## 整數運算 #### 無號整數加法 * 在加法的部份,因為類型能夠表達的數字大小有其範圍,如果兩者相加的數超過其範圍,多出來的部份就會在從 0 開始向上加。 ```clike #include <stdio.h> #define UN_MAX = 4294967295 int main() { unsigned number_one = UN_MAX; unsigned number_two = 2; unsigned sum = number_one + number_two; printf("%u\n", sum); return 0; } ``` * 用上面簡單的程式來做個範例,宣告 `unsigned` 變數,而 `unsigned` 的最大值為 $4294967295$,將 `number_one` 宣告為最大值,再加 $2$,得到結果並不是預期的 $4294967297$,結果是 $1$,這代表此結果產生 `overflow`  * 上圖是以 4 位元的無符號整數作為範例,在 4 位元的數其最大值為 15 , z 軸為平面 x 軸與 y 軸的總和,可以發現在當其兩軸總和超過 15 時就會從 0 開始繞回去往上加。 #### 有號整數加法 * 有號整數的 `overflow` 會有兩種,超出正值範圍的 `overflow` 或超出負值範圍 `overflow` * 如果 $x+y \geq 2^{w-1}$,其得到的結果就會是 $x+y-2^w$ ($2^{w-1}$ 是此類型最大值範圍),這樣為 `Positive overflow`。 * 如果 $x + y \leq -2^{w-1}$,其得到的結果就會是 $x+y+2^w$ ($-2^{w-1}$ 是此類型最小值範圍),這樣為 `negative overflow` * 實驗 ```clike #include <stdio.h> #define INT_MAX 2147483647 #define INT_MIN -2147483648 int main() { int number_one = INT_MAX; int number_two = 1; int sum = number_one + number_two; printf("INT_MAX + 1 = %d\n", sum); number_one = INT_MIN; number_two = -1; sum = -1 + INT_MIN; printf("-1 + INT_MIN = %d\n", sum); return 0; } ``` ```shell INT_MAX + 1 = -2147483648 -1 + INT_MIN = 2147483647 ``` 從上面的小實驗可以看出,當在其類型最大值再加一時,得到的值會是其最小值,而相反的最小值再加負一所得到的值為最大值。  從上圖就可以看出其 `Positive overflow` 得到的值,會從最小範圍上加,而 `Negative overflow` 則是從最大值開始下減。 :::info **延伸閱讀** 1. [解讀計算機編碼](https://hackmd.io/s/rylUqXLsm#) ::: > Reference > [Big-Endian 與Little-Endian 的差異與判斷程式碼- G. T. Wang](https://blog.gtwang.org/programming/difference-between-big-endian-and-little-endian-implementation-in-c/) > [Bits,Bytes,and Integers CMU CS:APP](http://www.cs.cmu.edu/afs/cs/academic/class/15213-f15/www/lectures/02-03-bits-ints.pdf)
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.