本章我們來研究三種重要的數(shù)字表示
-
無符號
是基于傳統(tǒng)二進(jìn)制表示法,表示大于或等于0的數(shù)字 -
補(bǔ)碼
是表示有符號整數(shù)的最常見方式,有符號整數(shù)可以是正或者為負(fù) -
浮點(diǎn)數(shù)
是表示實(shí)數(shù)的科學(xué)計數(shù)法的以2為基數(shù)的版本
計算機(jī)的表示法是用有限的數(shù)量的位表示的數(shù)字編碼,所以,結(jié)果太大的時候,某些運(yùn)算就會
溢出
。浮點(diǎn)運(yùn)算溢出會產(chǎn)生特殊的值+∞,但是一組正數(shù)的乘積總是正的,這點(diǎn)和整數(shù)不同。但是由于表示的精度有限,浮點(diǎn)運(yùn)算是不可結(jié)合的。整數(shù)的表示雖然只能編碼一個相對較小的數(shù)值范圍,但是這種表示是精確的,而浮點(diǎn)數(shù)可以編碼一個較大的數(shù)值范圍,但是這種表示只是近似的。
2.1信息存儲
大多數(shù)計算機(jī)使用8位的塊,或者說字節(jié)
,作為最小的可尋址的內(nèi)存單位,而不是訪問內(nèi)存中單獨(dú)的位。機(jī)器級程序?qū)?nèi)存視為一個非常大的字節(jié)數(shù)組,稱為虛擬內(nèi)存。內(nèi)存的每個字節(jié)都由一個唯一的數(shù)字標(biāo)識,稱為它的地址,所有可能地址的集合稱為虛擬地址空間。
十六進(jìn)制表示法
一個字節(jié)由8位組成,在二進(jìn)制表示法中,它的值域是0000000011111111,用十六進(jìn)制書寫,它的值域是0x000xFF。
以0x或者0X開頭的數(shù)字常量被認(rèn)為是16進(jìn)制,字符“A”~“F”既可以是大寫也可以是小寫。
字?jǐn)?shù)據(jù)大小
字長決定的最重要的系統(tǒng)參數(shù)就是虛擬地址空間的最大大小。對于一個字長為ω位的機(jī)器而言,虛擬地址的范圍就是0 ~ 2^ω - 1,程序最多訪問2^ω個字節(jié)。
大部分?jǐn)?shù)據(jù)類型都編碼為有符號數(shù)值,除非有前綴關(guān)鍵字unsigned或者對確定大小的數(shù)據(jù)類型使用了特定的無符號聲明。(數(shù)據(jù)類型char是個例外)
尋址和字節(jié)順序
多字節(jié)對象被存儲為連續(xù)的字節(jié)序列,對象的地址為所用字節(jié)最小的地址。
最低有效字節(jié)在最前面的方式,稱為小端法
。
最高有效字節(jié)在最前面的方式,稱為大端法
。
許多比較新的微處理器是雙端法,然而實(shí)際上,一旦選擇了特定的操作系統(tǒng),字節(jié)順序也就被固定下來。Android和iOS都是運(yùn)行于小端模式。
表示代碼
指令編碼是不同的,不同的機(jī)器類型使用不同的且不兼容的指令和編碼方式。
從機(jī)器的角度來看,程序僅僅只是字節(jié)序列。
有關(guān)布爾代數(shù)
布爾運(yùn)算~對應(yīng)邏輯運(yùn)算NOT,對應(yīng)集合的補(bǔ);
布爾運(yùn)算&對應(yīng)邏輯運(yùn)算AND,對應(yīng)集合的并;
布爾運(yùn)算|對應(yīng)邏輯運(yùn)算OR,對應(yīng)集合的交;
布爾運(yùn)算^對應(yīng)邏輯運(yùn)算亦或。
布爾運(yùn)算的分配率:a & (b | c) = (a & b)|(a & c),a | (b & c) = (a | b)&(a | c)
另外,對于a ^ a = 0,因此還有(a ^ b) ^ a = b
這里有一些有意思的關(guān)于位運(yùn)算的東西,簡單整理在一起,以后單開一個筆記,把一些位運(yùn)算的算法題歸類一下。
a ^= b;
b ^= a;
a ^= b;
這樣可以交換a和b的值,但是這個種交換方式?jīng)]有性能上的優(yōu)勢,它僅僅是一個智力游戲(至少書上是這么說的……)
有關(guān)邏輯運(yùn)算
C語言還提供了一組邏輯運(yùn)算符||、&&、和!,分別對應(yīng)命題邏輯中的OR,AND和NOT運(yùn)算。
邏輯運(yùn)算具有短路性。即:第一個參數(shù)求值能確定表達(dá)結(jié)果,那么就不會對第二個參數(shù)求值。
有關(guān)位移運(yùn)算
C語言提供了位移運(yùn)算,向左或者向右移動位模式。
C表達(dá)式x << k會x向左移動k位,丟棄最高的k位,并在右端補(bǔ)k個0.
有一個相應(yīng)的友誼運(yùn)算x >> k。一般而言,機(jī)器支持兩種形式的右移:邏輯右移和算術(shù)右移。邏輯右移在左端補(bǔ)k個0,算數(shù)右移是在左端補(bǔ)k個最高有效位的值。
對于無符號數(shù),右移必須是邏輯的。
Java表達(dá)式中,x >> k是算術(shù)右移,x >>> k是邏輯右移。
加減法優(yōu)先級比位移優(yōu)先級要高
一些位運(yùn)算的常見技巧
0 ^ a = a
a ^ a = 0
a & (a - 1)可以消除最右邊的一個1
位運(yùn)算x & 0xFF生成一個由x的最低有效字節(jié)組成的值,而其他字節(jié)就被置為0。
Java是用的補(bǔ)碼,0x7fffffff是最大正整數(shù),0x80000000是最小的負(fù)數(shù)。
和0x7fffffff按位與就是取絕對值了,然后那個按位或就是求負(fù)數(shù)。
可以用if ((a & 1) == 0)代替if (a % 2 == 0)來判斷a的奇偶性。
修改正負(fù)號就是按位取反再加一,也就是~x + 1
檢驗(yàn)補(bǔ)碼乘法是否溢出:
int p = x * y;
return !x || p / x == y;
2.2整數(shù)表示
整型數(shù)據(jù)類型表示有限范圍的整數(shù)。
有一個很值得注意的特點(diǎn)是取值范圍是不對稱的,負(fù)數(shù)的范圍比正數(shù)的范圍大1.
無符號數(shù)的編碼
無符號數(shù)編碼詳見思維導(dǎo)圖。
補(bǔ)碼編碼
補(bǔ)碼編碼詳見思維導(dǎo)圖。
ω位補(bǔ)碼所能表示的值的范圍:最小是位向量[10……0],最大的值是位向量[01……1]。
注意:-1和UMax有同樣的位表示——全1的串。數(shù)值0在兩種表示方式中都是全0的串。
有符號數(shù)和無符號數(shù)之間的轉(zhuǎn)換
C語言允許在各種不同的數(shù)字?jǐn)?shù)據(jù)類型之間做強(qiáng)制轉(zhuǎn)換。
強(qiáng)制類型轉(zhuǎn)換的結(jié)果保持位值不變,只是改變了解釋這些位的方式。
補(bǔ)碼轉(zhuǎn)無符號數(shù)詳見思維導(dǎo)圖。
無符號數(shù)轉(zhuǎn)為補(bǔ)碼詳見思維導(dǎo)圖。
執(zhí)行一個運(yùn)算的時候,如果它的一個運(yùn)算數(shù)是有符號的而另一個是無符號的,那么C語言會隱式的將有符號數(shù)強(qiáng)制類型轉(zhuǎn)換為無符號數(shù),并假設(shè)兩個數(shù)都是非負(fù)的,來執(zhí)行運(yùn)算。
擴(kuò)展一個數(shù)字的位表示
要將一個無符號數(shù)轉(zhuǎn)換為一個更大的數(shù)據(jù)類型,只需要簡單的在表示的開頭添加0,這種運(yùn)算被稱為零擴(kuò)展
。
要將一個補(bǔ)碼數(shù)字轉(zhuǎn)換為一個更大的數(shù)據(jù)類型,可以執(zhí)行一個符號擴(kuò)展
,在表示中添加最高有效位的值。
一個有意思的地方:
當(dāng)把short轉(zhuǎn)換成unsigned時,我們要先改變大小,之后再完成從有符號到無符號的轉(zhuǎn)換。也就是說(unsigned)sx等價于(unsigned)(int)sx
截斷數(shù)字
將一個ω位的數(shù)字截斷為一個k位的數(shù)字,我們會丟棄最高的ω - k位。
補(bǔ)碼截斷也有相似的屬性,不過要將最高位轉(zhuǎn)換為符號位,也就是說,一個正數(shù)截斷了以后可能就變成了負(fù)數(shù)。
2.3整數(shù)運(yùn)算
無符號加法
詳見思維導(dǎo)圖
如果s沒有溢出,則可以肯定s ≥ x
無符號數(shù)求反詳見思維導(dǎo)圖
補(bǔ)碼加法
詳見思維導(dǎo)圖
對滿足TMinω ≤ x, y ≤ TMaxω 的x和y,另s = x + y,當(dāng)且僅當(dāng)x > 0,y > 0,但s ≤ 0時,計算s發(fā)生了正溢出。當(dāng)且僅當(dāng)x < 0,y < 0,但s ≥ 0時,計算s發(fā)生了負(fù)溢出。
補(bǔ)碼的非
詳見思維導(dǎo)圖
無符號乘法
詳見思維導(dǎo)圖
補(bǔ)碼乘法
詳見思維導(dǎo)圖
乘以常數(shù)
編譯器使用了一項(xiàng)重要優(yōu)化,試著用位移和加法運(yùn)算的組合來代替乘以常數(shù)因子的乘法。
考慮一組從位位置n到位位置m的連續(xù)的1(n≥m)我們可以用下面兩種不同形式中的一種來計算這些位對乘積的影響:
- 形式A:(x << n) + (x << n - 1) + ... + (x << m)
- 形式B:(x << (n + 1)) - (x << m)
除以2的冪
- 除以2的冪無符號除法:C變量x和k有無符號數(shù)值x和k,且0 ≤ k < ω,則x>>k產(chǎn)生數(shù)值,x / 2^k
- 除以2的冪補(bǔ)碼除法,向下舍入:C變量補(bǔ)碼x和無符號k,且0 ≤ k < ω,當(dāng)執(zhí)行算術(shù)位移時,x>>k產(chǎn)生數(shù)值x / 2^k
- 除以2的冪的補(bǔ)碼除法,向上舍入:C變量補(bǔ)碼x和無符號k,且0 ≤ k < ω,當(dāng)執(zhí)行算術(shù)位移時,(x + (1 << k)- 1) >> k產(chǎn)生數(shù)值x / 2^k。
遺憾的是,和乘法不同,我們不能用除以2的冪的出發(fā)來表示任意常數(shù)K的除法,所以除法絕大多數(shù)情況下指令會相當(dāng)慢。
2.4浮點(diǎn)數(shù)
二進(jìn)制小數(shù)
定義見思維導(dǎo)圖
IEEE浮點(diǎn)表示
IEEE浮點(diǎn)標(biāo)準(zhǔn)用V = (-1)^s * M * 2^E
- s:sign,符號,決定正負(fù)。
- M:significand,尾數(shù),通常是[1.0~2.0)范圍的小數(shù)
- E:exponent,階碼,就是次方數(shù)。
單精度浮點(diǎn)格式中,s,exp和frac字段分別為1位、k = 8位和n = 23位,得到一個32位的表示。雙精度浮點(diǎn)格式中,s、exp和frac字段分別為1位、k = 11位和n = 52位,得到一個64位的表示。
規(guī)格化的值
解碼字段被解釋為偏置
形式表示的有符號整數(shù)。階碼的值是E = e - Bias,其中e是無符號數(shù),而Bias是一個等于2^(k-1) - 1的偏置值。
小數(shù)子段frac被解釋為描述小數(shù)值f,其中0 ≤ f < 1,二進(jìn)制小數(shù)在最高有效為的左邊。尾數(shù)定義為M = 1 + f。這種方式也叫做隱含的以1開頭的表示
.
非規(guī)格化的值
階碼值是E = 1 - Bias,而尾數(shù)的值是M = f,也就是小數(shù)字段的值,不包含隱含的開頭的1.
非規(guī)格化數(shù)的另外一個功能是表示那些非常接近于0.0的數(shù)。它們提供了一種屬性,稱為逐漸溢出,其中,可能的數(shù)值分布均勻的接近于0.0.
特殊值
最后一類數(shù)值是當(dāng)階碼全為1的時候出現(xiàn)的。當(dāng)小數(shù)域全為0時,得到的值表示無窮,當(dāng)s = 0時是+∞,當(dāng)s = 1時是-∞。當(dāng)我們把兩個非常大的數(shù)相乘,或者除以零時,無窮能夠表示溢出
的結(jié)果。當(dāng)小數(shù)域?yàn)榉橇愕臅r候,結(jié)果值被稱為“NaN”,即“不是一個數(shù)(Not a Number)”的縮寫。一些運(yùn)算的結(jié)果不能是實(shí)數(shù)或無窮,就會返回這樣的NaN值,比如當(dāng)計算根號-1或者∞-∞時。
浮點(diǎn)數(shù)的一些Point
- 值+0.0總有一個全為0的位表示。
- 最小的正非規(guī)格化的位表示,是有最低有效位為1而其他所有位為0構(gòu)成的。它的數(shù)字值是V = 2(-n-2(k-1)+2)。
- 最大的非規(guī)格化值的位模式是由全為0的解碼字段和全為1的小叔子段組成的。數(shù)值V = (1 - 2^(-n)) * 2((-2)(k-1)+2)。
- 最小的正規(guī)格化值的位模式的階碼字段的最低有效位為1,其他位全為0。它的尾數(shù)值M = 1,而階碼值E = -2^(k-1) + 2。因此數(shù)值V = 2((-2)(k-1) + 2)。
- 值1.0的位表示的階碼字段除了最高有效為等于1以外,其他位都等于0。它的尾數(shù)值是M = 1,他的階碼值是E = 0.
- 最大的規(guī)格化值的位表示的符號位為0,階碼的最低有效位等于0,其他位等于1.數(shù)值V = (1 - 2^(-n-1)) * 22(k-1)
舍入
IEEE浮點(diǎn)格式定義了四種不同的舍入方式
。默認(rèn)的方法是找到最接近的匹配,其他三種可用于計算上界和下界。
浮點(diǎn)運(yùn)算
IEEE標(biāo)準(zhǔn)定義了一些使之更合理的規(guī)則。例如,定義1 / -0將產(chǎn)生-∞,而定義1 / +0會產(chǎn)生+∞
對于所有的x和y的值,實(shí)數(shù)的加法運(yùn)算是可交換的。但是運(yùn)算是不可結(jié)合的。例如(3.14 + 1e10)- 1e10
無窮(因?yàn)?∞ - ∞ = NaN)和 NaN是例外情況,因?yàn)閷τ谌魏蝬都有NaN + x = NaN。
浮點(diǎn)加法滿足了單調(diào)性屬性:如果a ≥ b,那么對任何a、b以及x的值,除了NaN,都有x + a ≥ x + b。
浮點(diǎn)乘法也遵循通常乘法所具有的許多屬性。它是可交換的,不具有可結(jié)合行。例如(1e20 * 1e20) * 1e-20位+∞,而1e20 * (1e20 * 1e-20)得出1e20。
浮點(diǎn)乘法滿足單調(diào)性:
a ≥ b 且 c ≥ 0 → a * c ≥ b * c
a ≥ b 且 c ≤ 0 → a * c ≤ b * c
我們還可以保證,只要a ≠ NaN,就有a * a ≥ 0.
C語言中的浮點(diǎn)數(shù)
所有C語言版本提供了兩種不同的浮點(diǎn)數(shù)據(jù)類型:float 和 double。
一些會產(chǎn)生錯誤的點(diǎn):
- 從int轉(zhuǎn)成float,數(shù)字不會溢出,但是可能被舍入。
- 從int或float轉(zhuǎn)成double,因?yàn)閐ouble有更大的范圍,也有更高的精度,所以能夠保留精確的數(shù)值。
- 從double轉(zhuǎn)換成float,因?yàn)榉秶。灾悼赡芤绯龀?∞或者-∞,另外,也可能舍入。
- 從float或double轉(zhuǎn)成int,值將會向零舍入。例如1.999轉(zhuǎn)換為1,而-1.999轉(zhuǎn)為-1.進(jìn)一步來說,值可能會溢出。與Inter兼容的微處理器指定位模式[10...00](字長為ω時的TMinω)為
整數(shù)不確定
值。一個從浮點(diǎn)數(shù)到整數(shù)的轉(zhuǎn)換,如果不能為該浮點(diǎn)數(shù)找到一個合理的整數(shù)近似值,就會產(chǎn)生一個這樣的值。因此,表達(dá)式(int)+le10會得到-21483648,即從一個正值變成了一個負(fù)值!!!
課后題中的一些點(diǎn)
- 表達(dá)式~0xFF創(chuàng)建一個掩碼,該掩碼8個最低位為0,其余的為1.這個掩碼的產(chǎn)生與字長無關(guān)。相比之下,0xFFFFFF00只能工作在32位機(jī)器上。
- x ^ y = (x & ~y) | (~x & y)
- TMin32是-2147483648,并且將它強(qiáng)制類型轉(zhuǎn)換為無符號數(shù)后,變成了2147483648。另外,如果有任何一個運(yùn)算數(shù)是無符號的,那么在比較之前,另一個運(yùn)算數(shù)會被強(qiáng)制類型轉(zhuǎn)換為無符號數(shù)。
- 函數(shù)的任何測試過程中,TMin 都應(yīng)該作為一種測試情況!!!
- 對于無符號數(shù)的非,位的模式是相同的。
- int64_t pll = (int64_t) x * y;這一句如果寫成int64_t pll = x * y;就會用32位的值來計算乘積(這時候可能就溢出了!),然而再符號擴(kuò)展到64位。
- 表達(dá)式x>>32產(chǎn)生一個字,如果x是負(fù)數(shù),這個字全為1,否則全為0.這個訣竅偶爾會有大用處。
- 一個具有n位小數(shù)的浮點(diǎn)格式,不能準(zhǔn)確描述的最小正整數(shù)是:1后面跟著n個0后面再跟一個1.即2^(n+1) + 1
- 2.44C的謎題里的需要理解的計算機(jī)運(yùn)算的屬性。
32位機(jī)器,有符號值使用算術(shù)右移,無符號值使用邏輯右移
int x,y
unsigned ux,uy
A. (x > 0)||((x - 1) < 0
False 假設(shè)x = TMin32,那么x - 1 = TMax32
B. (x & 7) != 7 || (x << 29 < 0)
True
C. (x * x) >= 0
False 當(dāng)x為0xFFFF時,x * x為-131071(0xFFFE0001)
D. x < 0 || -x <= 0
True
E. x > 0 || -x >= 0
False 設(shè)x = TMin32(好吧,又是它)那么x和-x都是負(fù)數(shù)!
F. x + y == uy + ux
True
G. x * ~y + uy * ux == -x
True ~y等于-y-1.uy * ux等于x * y,所以左邊等價于x * -y - x + x * y。
勘誤:43頁第一段原文中寫的是復(fù)數(shù)的范圍比整數(shù)的范圍大1,應(yīng)該是正數(shù)……