本應該之前整理好的,又拖到現在,不管怎麼樣繼續堅持看下去,從二章開始就越來越不好理解了 整數運算 再次來看之前的一個例子: 還是通過這裡例子來看這個部分的知識點 無符號加法 無符號加法原理: 其實每次看到這種原理推導過程自己基本都不怎麼願意去看,不過我們可以通過實際的例子來好好理解,來幫助自己更好的 ...
本應該之前整理好的,又拖到現在,不管怎麼樣繼續堅持看下去,從二章開始就越來越不好理解了
整數運算
再次來看之前的一個例子:
root@localhost: lldb (lldb) print (500 * 400) * (300 * 200) (int) $0 = -884901888 (lldb) print ((500 * 400)* 300) * 200 (int) $1 = -884901888 (lldb) print ((200 * 500) * 300) * 400 (int) $2 = -884901888 (lldb) print 400 * (200 * (300 * 500)) (int) $3 = -884901888 (lldb)
還是通過這裡例子來看這個部分的知識點
無符號加法
無符號加法原理:
其實每次看到這種原理推導過程自己基本都不怎麼願意去看,不過我們可以通過實際的例子來好好理解,來幫助自己更好的理解
通過一個確定的4位的無符號數來看,如果x = 9 y = 12 x和y的二進位表示分別為[1001] 和 [1100] 他們相加的和為21 這個時候其實超過了我們最開始設定的4位,需要用5位來表示也就是[10101] 如果我們丟掉了最高位,就成了[0101] 轉換為十進位就是5 其實這個值的計算結果就是21 對16 求餘 得到5是一致的
這個時候我們在來看上面的公式原理,其實就是當你兩個數相加已經超過了最大位數的時候,最高位就會被捨棄,即當結果溢時需要捨棄最高位的值
無符號求反
還是先看原理:
這裡其實我自己有點小疑惑,因為剛開始的時候,我理解的無符號數求反,是把一個數的二進位表示方式求反得到的值,這樣吧,通過一個實際的例子來理解:
對於12 二進位為[1100] 我理解的求反得到的是[0011]這樣得到的數是3
但是這裡講的無符號數求反,其實是通過2的4次方 減去 12 得到的是 4
所以這裡有點不確定,這個求反,後續再查資料看看是怎麼回事
補碼加法
這裡第一次看的時候沒有理解,不過後來又過了幾天再看了一下理解了(可能之前看的時候,一眼看去都是公式,自己就不想看)
既然是補碼的加班,先回顧一下補碼的最大值和最小值
對於一個w為的補碼數來說,能表示的最小值為:-2的w-1次方, 表示的最大值為:2的w-1次方 減1
在給定範圍x>=-2的w-1次方, y <= 2的w-1次方減1 它們的和的範圍就是-2的w次方<=x+y<=2的w次方減2
來吧,先看一下原理:
當x+y的值超過的補碼的最大值的時候即大於TMax的時候是正溢出,需要減去2的w次方
當x+y的值小於補碼的最小值的時候即小於Tmin的時候是負溢出,需要加上2的w次方
補碼的非
還是先看原理:
其實對於補碼的非有個簡單的方法
先看幾個實際的例子:
總結為一句話就是:對每一位求補,在對其結果加1
其實還有一種方法,還是通過一些例子理解:
其實總結一下就是:找到最右邊的1,然後這個1的左邊的所有位進行取反
無符號乘法
無符號的最大值的表示是2的w次方減1,那麼對於x >=0 y <= 2的w次方減1,x和y的乘積的取值範圍就是0到 (2的w次方減1)的平方, 這樣可能就會需要2w位來表示,C語言中的無符號乘法被定義為產生w為的值,就是2w位的整數乘積的低w位表示的值
來看看原理為:
補碼乘法
還是先看原理:
其實對於無符號和補碼乘法來說乘法的位級運算都是一樣的
通過下麵這個實際的例子,就會更加清楚:
乘以2的冪
早些時候,在大多數機器上,整數的乘法指令是非常慢的,所以編譯器對此作了優化,通過位移和加法運算的組合方式來代替乘以常數因數的乘法
原理如下:
一般原理看起來都不容易理解,或者估計很多人和我剛開始看的時候一樣也是直接忽略,所以還是結合例子來看:
還是以w=4來看,即4位, 11可以通過二進位[1011]表示,k=2 時,將其左移到6位向量得到[101100],即編碼為無符號數11*4 = 44
無論是無符號運算還是補碼運算,乘以2的冪都可以能會導致溢出。但是即使溢出的時候,通過位移得到的結果也是一樣的
由於整數乘法比位移和加法的代價要大的多,許多c語言編譯器試圖以位移、加法和減法的組合來消除很多整數乘以常數的情況,一個例子:
x * 14 利用14 = 2的3次方 + 2的2次方 + 2的1次方 編譯器會講乘法重寫為(x<<3) + (x<<2) + (x<<1)
無論x是無符號還是補碼,甚至當乘法會導致溢出時,兩個計算都會得到一樣的結果
設置編譯器還可以利用14 = 2的4次方 - 2的1次方 將乘法重寫為(x<<4)-(x<<1)
下麵是一個例子:
中間的移位表示要有幾個移位,後面的加法/減法表示做幾次加法或者減法
除以2的冪
大多數機器上,整數除法要比整數乘法更慢,需要30個或者更多的時鐘周期
除以2的冪也可以用移位運算來實現,不過這裡用的是右移,而不是左移
無符號和補碼分別使用邏輯移位和算數移位來達到目的
來看原理:
下麵是在12340的16位表示上執行邏輯右移的結果對它執行邏輯右移的結果,以及對它執行除以1,2,16,和256的結果
當x>=0, 變數x的最高有效位為0,所以效果與邏輯右移是一樣的,因此對於非負數來說,算術右移k位,和除以2的k次方是一樣的
下圖是-12340的16位表示進行算術右移不同位數的結果。對於不需要舍入的情況結果是x/2的k次方
當時當需要進行舍入的時候,位移導致結果向下舍入入右移4位會把-771.25向下舍入為-772
關於除以2的冪的補碼除法,向上舍入不是非常理解,後面需要再看
在執行算術右移之前加上一個適當的偏執量來修正舍入,看下圖:
在第三列,給出了-12340加上偏量值之後的結果,低k位以斜體表示,可以看出,低k位左邊的位可能會加1,也可能不會加1,對於不需要舍入的情況k=1,加上偏量只會影響那些被移掉的位,對於需要舍入的情況,加上偏量導致較高的位加1,所以結果會向零舍入
關於整數運算的小結
電腦執行的整數運算實際上是一種模運算形式,表示數字的有限字長限制了可能的值的取值範圍,結果可能溢出。
同時補碼表示還提供了一種既能表示負數,也能表示正數的靈活方法,使用了與執行無符號算術相同的位級實現,包括:加法,減法,乘法,除法,無論運算是以無符號形式還是以補碼形式,都完全一樣活著非常類似的位級行為
浮點數
浮點數可以用統一的公式表示:
如:
並且可以看到除以2 就相當於右移,並且可以橫跨小數點
當時這種表示是有問題的,如:x/2的k次方的數可以精確表示,其他數字會變成迴圈小數
如1/3 = 0.0101010101[01]....
IEEE浮點數標準
IEEE標準中,用下麵公式表示浮點數
符號(sign):s是符號位,決定正負
尾數(singificand):M是一個二進位小數,它的範圍通常是[1.0,2.0]
階碼(exponent):E 的作用是對浮點數加權,這個權重是2的E次冪
其中 s 對應著符號位,exp 對應著 E,frac 對應著 M。不同的位數就代表了不同的表示能力,也就是單精度,雙精度,擴展精度的來源。
規範化值
在 exp≠000…0 和 exp≠111…1 時,表示的其實都是規範化的值
再來看公式:
這裡的 E 是一個偏移的值 E=Exp−Bias
Exp: 是 exp 編碼區域的無符號數值
Bias:值為 2的k-1次方−1 的偏移量,其中 k 是 exp 編碼的位數,也就是說
單精度:127(Exp: 1…254, E: -126…127)
雙精度:1023(Exp: 1…2046, E: -1022…1023)
之所以需要採用一個偏移量,是為了保證 exp 編碼只需要以無符號數來處理。
而對於 M,一定是以 1 開頭的:也就是 M=1.xxx…x。其中 xxx 的部分就是 frac 的編碼部分,當 frac=000.00 的時候值最小(M=1.0),當 frac=111。。。1 的時候值最大(M=2.0−ϵ)
一個例子:
float F = 15213.0
用上面方法理解:
15213=11101101101101=1.1101101101101×2的13次方
於是 frac 部分的值就是小數點後面的數值,而 Exp = E + Bias = 13 + 127 = 140 = 10001100,於是編碼出來的浮點數是這樣的:
0 10001100 11011011011010000000000
s exp frac
非規範化值
當 exp=000…0 的時候,值是非規範化的,意思是,雖然實數軸上原來連續的值會被規範到有限的定值上,但是並些定值之間的間距也是一樣的
和前面不同的是
E=1−Bias
而且 M=0.xxx…x,不是以 1 開頭了。
當 exp=000…0 且 frac = 000…0 時,表示 0,而且因為符號位的緣故,實際上是有 +0 和 -0 兩種的。而在 exp=000..0 且 frac≠000…0 時,數值是接近 0 的,並且間距是一致的
特殊值
還有一種特殊情況,就是 exp=111…1 時,表示一些特殊值。
當 exp=111…1 且 frac = 000…0 時,表示 ∞,而且因為符號位的緣故,實際上是有 +∞ 和 −∞ 兩種的。那些會溢出的操作就會用這個來表示,比如 1.0/0.0=−1.0/0.0=+∞,1.0/−0.0=−∞
而在 exp=111…1 且 frac≠000…0 時,我們認為這不是一個數值(Not-a-Number,NaN),用來表示那些沒辦法確定的值,比如 sqrt(−1),∞−∞,∞×0
通過例子理解:
接下來舉一個實際的例子,我們採用 1 位符號位,4 位 exp 位,3 位 frac 位,因此對應的 bias 為 7。回顧一下幾個重要公式:
對於規範化數:E=Exp−Bias;對於非規範數:E=1−Bias,正數部分的數值為
s exp frac E 值 ------------------------------------------------------------------ 0 0000 000 -6 0 # 這部分是非規範化數值,下一部分是規範化值 0 0000 001 -6 1/8 * 1/64 = 1/512 # 能表示的最接近零的值 0 0000 010 -6 2/8 * 1/64 = 2/512 ... 0 0000 110 -6 6/8 * 1/64 = 6/512 0 0000 111 -6 7/8 * 1/64 = 7/512 # 能表示的最大非規範化值 ------------------------------------------------------------------ 0 0001 000 -6 8/8 * 1/64 = 8/512 # 能表示的最小規範化值 0 0001 001 -6 9/8 * 1/64 = 9/512 ... 0 0110 110 -1 14/8 * 1/2 = 14/16 0 0110 111 -1 15/8 * 1/2 = 15/16 # 最接近且小於 1 的值 0 0111 000 0 8/8 * 1 = 1 0 0111 001 0 9/8 * 1 = 9/8 # 最接近且大於 1 的值 0 0111 010 0 10/8 * 1 = 10/8 ... 0 1110 110 7 14/8 * 128 = 224 0 1110 111 7 15/8 * 128 = 240 # 能表示的最大規範化值 ------------------------------------------------------------------ 0 1111 000 n/a 無窮 # 特殊值
從上面可以看出:
在 exp=0000 時,也就是非規範化的情況,間距是一致的,都是 1/8
因為位數的限制,從零到一之間的數字只能以 1/8 為最小單位來表示,且相鄰數字間間距一樣
在規範化的部分,可以發現由於 exp 部分的不同,所以相鄰數字間的間隔也是不同的,比方說最接近 1 的數字是 15/16 和 9/8,分別相差 1/16 和 1/8,這也是由於 IEEE 浮點數表示法的公式決定的
舍入
對於浮點數的加法和乘法來說,我們可以先計算出準確值,然後轉換到合適的精度。在這個過程中,既可能會溢出,也可能需要舍入來滿足 frac 的精度。
在二進位中,我們舍入到最近的偶數,即如果出現在中間的情況,舍入之後最右邊的值要是偶數,對於十進位數,例子如下:
原數值 舍入結果 原因 2.8949999 2.89 不到一半,正常四捨五入 2.8950001 2.90 超過一半,正常四捨五入 2.8950000 2.90 剛好在一半時,保證最後一位是偶數,所以向上舍入 2.8850000 2.88 剛好在一半時,保證最後一位是偶數,所以向下舍入
小結
電腦將信息編碼為位(比特),通常組織成位元組序列。不同的編碼方式用來表示整數,實數和字元串
大多數機器對整數使用補碼編碼,對於浮點數使用IEEE標準編碼
由於編碼的長度有限,電腦運算具有不同的屬性,當超過表示範圍時,有限長度能夠引出數值溢出。
當浮點數非常接近0.0 從而轉換為0時,也會下溢
整體上第二章花了很多時間看,但是其實對很多知識還是沒有做到完全理解,後面可以回過頭重新來看,可能那個時候會有新的理解,也能更加深刻