編寫c語言的**時常見的問題之一就是不同字長的整數相互轉換直接容易引發潛在的錯誤。例如低字長整數轉換為高字長整數會發生隱式位擴充套件,而高字長整數轉換為低字長整數會發生隱式位截斷。可能你會疑惑為什麼這裡只有整數而沒有浮點數,這是因為浮點數在位模式上採用了與整數不同的表示方式,這在之後會討論。
在對整數進行位擴充套件時,對無符號數進行的是零擴充套件,對有符號數進行的是符號擴充套件。
無符號數
無符號數進行的是零擴充套件,顧名思義就是往高位新增0,例如將8位擴充套件至16位有如下表現:
數字擴充套件前
擴充套件後7
0000 0111
0000 0000 0000 0111
390010 0111
0000 0000 0010 0111
1321000 0100
0000 0000 1000 0100
2001100 1000
0000 0000 1100 1000
有符號數
有符號數進行的是符號擴充套件,也就是將最高位使用符號位進行擴充套件,例如將8位擴充套件至16位有如下表現:
數字擴充套件前
擴充套件後7
0000 0111
0000 0000 0000 0111
390010 0111
0000 0000 0010 0111
-124
1000 0100
1111 1111 1000 0100
-561100 1000
1111 1111 1100 1000
在對整數進行位截斷是,無符號數和有符號數具有相同的表現,只是對有符號數我們仍然需要將最高位看作符號位。
無符號數
將無符號數截斷採用的是直接截斷,例如將16位無符號數截斷位8位無符號數有如下表現:
數字截斷前
截斷後7
0000 0000 0000 0111
0000 0111
390000 0000 0010 0111
0010 0111
1320000 0000 1000 0100
1000 0100
2000000 0000 1100 1000
1100 1000
有符號數
對有符號數截斷也同樣是直接截斷,但仍然需要將最高位看作符號位,例如將16位有符號數截斷位8位有符號數有如下表現:
數字截斷前
截斷後7
0000 0000 0000 0111
0000 0111
390000 0000 0010 0111
0010 0111
-124
1111 1111 1000 0100
1000 0100
-561111 1111 1100 1000
1100 1000
你可能會發現以上的截斷均保持了數字大小的不變,這是因為選擇的數字比較合理,你可以輕鬆的嘗試出幾個截斷後會造成資料大小改變的例子,例如:
數字截斷前
截斷後-2048
1111 1000 0000 0000
(0000 0000)\(_\) 0\(_\)
1904
0000 0111 0111 0000
(0111 0000)\(_\) 112\(_\)
7700000 0011 0000 0010
(0000 0010)\(_\) 2\(_\)
32767
0111 1111 1111 1111
(1111 1111)\(_\) -1\(_\)
對無符號數和有符號數執行加法時,它們在位模式上有相同的表現,同樣對於有符號數要將最高位看作符號位。對\(-2^ \le x,y \le 2^-1\)的兩個數字進行相加是,顯然可能造成溢位,對於溢位的解決辦法是將溢位部分截斷,僅僅保留低\(w\)位。例如對4位數的加法有如下表現:
注:括號下標為2表示是二進位制數,下標為t表示是補碼,下表為u表示是無符號數x
yx+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\)
減法與加法相同,只是要將減數當成負數做加法即可。只要從位的模式上來看待數字的加法,就不容易因為各種溢位的情況而計算出錯。
對無符號數求反我們有如下定義:
\[f(x)=\left\ x & & x=0 \\2^w-x & & x>0\end \right.
\]對滿足\(tmin_w \le x \le tmax_w\)的\(x\),其補碼求反我們有如下定義:
\[f(x)=\left\ tmin_w & & x=tmin_w \\-x & & x>tmin_w\end \right.
\]看上去兩個定義差別很大,但實際上對於無符號數和補碼求反在位模式上是相同的,均為對每一位求反再加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
對於除法,與乘法類似,例如乘法在位模式上是往左移位,而相似的除法就是向右移位。
資訊的表示 一
現代計算機儲存和處理的資訊均以二值訊號表示。對於人來說,十進位制已經完全夠了,但對於計算機來說,二進位制會表現得更好,為什麼可以參考 從編碼到二進位制 一文。不同的數字有著不同的含義,這個含義是我們人去定義的,計算機如何理解,需要人去告訴它。對於不同的程式語言,計算機會有不同的理解方式。在此我們主要...
二進位制資訊表示
就是普通 的乙個整數 可能為負數 這個整數占用4個位元組空間 如何儲存到char型別的字元型陣列裡面,再按照單個位元組讀取的形式,在電腦本地,將資料讀取出來。比較簡單粗暴的方法 1.迴圈讀取,每次把整數的最小8位存進去,然後右移8位。比較直接的方法就是 char chrarr 10 int intv...
CSI III 資訊的表示與處理 數值陷阱 二
談到浮點數,或許你能想到ieee 讀作 eye triple eee 754標準。這個標準的制定是從1976年開始由intel贊助的,在8087設計的同時,8087是一種為8086處理器提供浮點支援的晶元。他們請kahan作為顧問,幫助設計未來處理器浮點標準。並支援kahan加入ieee委員會,後來...