信息的表示(二)
整數的擴展與截斷
編寫C語言的代碼時常見的問題之一就是不同字長的整數相互轉換直接容易引發潛在的錯誤。例如低字長整數轉換為高字長整數會發生隱式位擴展,而高字長整數轉換為低字長整數會發生隱式位截斷。可能你會疑惑為什么這里只有整數而沒有浮點數,這是因為浮點數在位模式上采用了與整數不同的表示方式,這在之后會討論。
位擴展
在對整數進行位擴展時,對無符號數進行的是零擴展,對有符號數進行的是符號擴展。
無符號數
無符號數進行的是零擴展,顧名思義就是往高位添加0,例如將8位擴展至16位有如下表現:
數字 | 擴展前 | 擴展后 |
---|---|---|
7 | 0000 0111 | 0000 0000 0000 0111 |
39 | 0010 0111 | 0000 0000 0010 0111 |
132 | 1000 0100 | 0000 0000 1000 0100 |
200 | 1100 1000 | 0000 0000 1100 1000 |
有符號數
有符號數進行的是符號擴展,也就是將最高位使用符號位進行擴展,例如將8位擴展至16位有如下表現:
數字 | 擴展前 | 擴展后 |
---|---|---|
7 | 0000 0111 | 0000 0000 0000 0111 |
39 | 0010 0111 | 0000 0000 0010 0111 |
-124 | 1000 0100 | 1111 1111 1000 0100 |
-56 | 1100 1000 | 1111 1111 1100 1000 |
位截斷
在對整數進行位截斷是,無符號數和有符號數具有相同的表現,只是對有符號數我們仍然需要將最高位看作符號位。
無符號數
將無符號數截斷采用的是直接截斷,例如將16位無符號數截斷位8位無符號數有如下表現:
數字 | 截斷前 | 截斷后 |
---|---|---|
7 | 0000 0000 0000 0111 | 0000 0111 |
39 | 0000 0000 0010 0111 | 0010 0111 |
132 | 0000 0000 1000 0100 | 1000 0100 |
200 | 0000 0000 1100 1000 | 1100 1000 |
有符號數
對有符號數截斷也同樣是直接截斷,但仍然需要將最高位看作符號位,例如將16位有符號數截斷位8位有符號數有如下表現:
數字 | 截斷前 | 截斷后 |
---|---|---|
7 | 0000 0000 0000 0111 | 0000 0111 |
39 | 0000 0000 0010 0111 | 0010 0111 |
-124 | 1111 1111 1000 0100 | 1000 0100 |
-56 | 1111 1111 1100 1000 | 1100 1000 |
你可能會發現以上的截斷均保持了數字大小的不變,這是因為選擇的數字比較合理,你可以輕松的嘗試出幾個截斷后會造成數據大小改變的例子,例如:
數字 | 截斷前 | 截斷后 |
---|---|---|
-2048 | 1111 1000 0000 0000 | (0000 0000)\(_{2}\) 0\(_{10}\) |
1904 | 0000 0111 0111 0000 | (0111 0000)\(_{2}\) 112\(_{10}\) |
770 | 0000 0011 0000 0010 | (0000 0010)\(_{2}\) 2\(_{10}\) |
32767 | 0111 1111 1111 1111 | (1111 1111)\(_{2}\) -1\(_{10}\) |
整數運算
加法
對無符號數和有符號數執行加法時,它們在位模式上有相同的表現,同樣對於有符號數要將最高位看作符號位。對\(-2^{w-1} \le x,y \le 2^{w-1}-1\)的兩個數字進行相加是,顯然可能造成溢出,對於溢出的解決辦法是將溢出部分截斷,僅僅保留低\(w\)位。例如對4位數的加法有如下表現:
注:括號下標為2表示是二進制數,下標為t表示是補碼,下表為u表示是無符號數
x | y | x+y截斷前 | x+y截斷后 |
---|---|---|---|
(1000)\(_2\) (-8)\(_t\) (8)\(_u\) | (1011)\(_2\) (-5)\(_t\) (11)\(_u\) | (10011)\(_2\) (-13)\(_t\) (19)\(_u\) | (0011) (3)\(_t\) (3)\(_u\) |
(1000)\(_2\) (-8)\(_t\) (8)\(_u\) | (1000)\(_2\) (-8)\(_t\) (8)\(_u\) | (10000)\(_2\) (-16)\(_t\) (16)\(_u\) | (0000) (0)\(_t\) (0)\(_u\) |
(1000)\(_2\) (-8)\(_t\) (8)\(_u\) | (0101)\(_2\) (5)\(_t\) (5)\(_u\) | (11101)\(_2\) (-3)\(_t\) (29)\(_u\) | (1101) (-3)\(_t\) (13)\(_u\) |
(0010)\(_2\) (2)\(_t\) (2)\(_u\) | (0101)\(_2\) (5)\(_t\) (5)\(_u\) | (00111)\(_2\) (7)\(_t\) (7)\(_u\) | (0111) (7)\(_t\) (7)\(_u\) |
(0101)\(_2\) (5)\(_t\) (5)\(_u\) | (0101)\(_2\) (5)\(_t\) (5)\(_u\) | (01010)\(_2\) (10)\(_t\) (10)\(_u\) | (1010) (-6)\(_t\) (10)\(_u\) |
減法與加法相同,只是要將減數當成負數做加法即可。只要從位的模式上來看待數字的加法,就不容易因為各種溢出的情況而計算出錯。
求反
對無符號數求反我們有如下定義:
對滿足\(TMin_w \le x \le TMax_w\)的\(x\),其補碼求反我們有如下定義:
看上去兩個定義差別很大,但實際上對於無符號數和補碼求反在位模式上是相同的,均為對每一位求反再加1。例如以下:
無符號數
\(\vec x\) | ~\(\vec x\) | \(incr(\) ~ \(\vec x)\) |
---|---|---|
0101 5 | 1010 10 | 1011 11 |
0111 7 | 1000 8 | 1001 9 |
1100 12 | 0011 3 | 0100 4 |
0000 0 | 1111 15 | 0000 0 |
1000 8 | 0111 7 | 1000 8 |
有符號數
\(\vec x\) | ~\(\vec x\) | \(incr(\) ~ \(\vec x)\) |
---|---|---|
0101 5 | 1010 -6 | 1011 -5 |
0111 7 | 1000 -8 | 1001 -7 |
1100 -4 | 0011 3 | 0100 -4 |
0000 0 | 1111 -1 | 0000 0 |
1000 -8 | 0111 7 | 1000 -8 |
乘法
對於無符號和補碼的乘法來說,位模式的表現都是一樣的,對一個字長為\(w\)的數字\(x\),結果均只保留低\(w\)位,高位直接截斷,但是對於位模式來說,直接乘法運算要比加法運算消耗更多的時間,因此在許多的編譯器中,乘法運算都將會被優化為移位運算。例如\(x *8\)將會被編譯器優化為\(x \ll 3\) ,即表示\(x * 2^3\),同理\(x*15\) 將會被優化為\(x\ll3 + x \ll 2 + x \ll 1 + x \ll 0\),即表示\(x*2^3+x*2^2+x*2^1+x*2^0\)。有一下示例:
模式 | \(x\) | \(y\) | \(x*y\) | 截斷后的\(x*y\) |
---|---|---|---|---|
無符號 | 5 101 | 3 011 | 15 001111 | 7 111 |
補碼 | -3 101 | 3 011 | -9 110111 | -1 111 |
無符號 | 4 100 | 7 111 | 28 011100 | 4 100 |
補碼 | -4 100 | -1 111 | 4 000100 | -4 100 |
無符號 | 3 011 | 3 011 | 9 001001 | 1 001 |
補碼 | 3 011 | 3 011 | 9 001001 | 1 001 |
對於除法,與乘法類似,例如乘法在位模式上是往左移位,而相似的除法就是向右移位。