- 計(jì)算機(jī)系統(tǒng)解密:從理解計(jì)算機(jī)到編寫高效代碼
- (美)喬納森·E.斯坦哈特
- 4282字
- 2021-09-27 16:56:57
1.5 用比特表示整數(shù)
讓我們沿著邏輯鏈往上走,學(xué)習(xí)如何用比特來(lái)表示數(shù)字。數(shù)字比邏輯復(fù)雜,但比文字簡(jiǎn)單得多。
1.5.1 表示正數(shù)
我們常用十進(jìn)制數(shù)字系統(tǒng),因?yàn)樗鼘?duì)應(yīng)我們的十根手指。十個(gè)不同的稱為數(shù)字的符號(hào)(0123456789)可以放進(jìn)容器。容器從右到左依次堆放。每個(gè)容器都有一個(gè)與內(nèi)容無(wú)關(guān)的名稱,位于最右邊的容器稱為“一”,接下來(lái)是“十”,然后是“百”“千”,以此類推。這些名稱是10的冪的別名,100是1,101是10,102是100,103是1 000。由于10是這些指數(shù)的底數(shù),這個(gè)系統(tǒng)被稱為十進(jìn)制系統(tǒng)。數(shù)字的值等于每個(gè)容器的值與容器內(nèi)的值的乘積之和。例如,數(shù)字5 028是5個(gè)1 000、0個(gè)100、2個(gè)10、8個(gè)1的總和,即5×103+0×102+2×101+8×100,如圖1-5所示。

圖1-5 數(shù)字5 028的十進(jìn)制記數(shù)法
我們可以按照類似的方法用比特來(lái)表示數(shù)字。由于用的是比特而不是數(shù)字,因此只有兩個(gè)符號(hào)0和1,但這不是問題。在十進(jìn)制中,每當(dāng)空間不夠用時(shí),就會(huì)增加一個(gè)容器。比如我們可以把9裝在一個(gè)容器中,但裝10需要兩個(gè)容器。這也適用于二進(jìn)制容器,任何大于1的數(shù),都需要再來(lái)一個(gè)新的容器。最右邊的容器仍然是1,但它的左邊是什么容器?就是2!在十進(jìn)制中,有10個(gè)符號(hào),因此容器的值是右邊那個(gè)容器的10倍。由于在二進(jìn)制中只有兩個(gè)符號(hào),因此容器的值是右邊那個(gè)容器值的兩倍。這就是它的全部?jī)?nèi)容!容器的值都是2的冪,這意味著二進(jìn)制容器是底數(shù)為2的系統(tǒng),而不是底數(shù)為10的系統(tǒng)。
表1-1列出了一些2的冪,我們可以用它作為參考,來(lái)理解數(shù)字5 028的二進(jìn)制表示法。
表1-1 2的冪

表1-1中最右邊的每一個(gè)數(shù)字代表一個(gè)二進(jìn)制容器的值。圖1-6展示了數(shù)字5 028是如何用二進(jìn)制表示的,其過程與前面的十進(jìn)制記數(shù)法的表示過程基本相同。

圖1-6 數(shù)字5 028的二進(jìn)制形式
轉(zhuǎn)成二進(jìn)制的結(jié)果是:
1×212 + 0×211 + 0×210 + 1×29 + 1×28 + 1×27 + 0×26 + 1×25 + 0×24 + 0×23 + 1×22 + 0×21 + 0×20 = 5 028
正如你所看到的,二進(jìn)制數(shù)5 028有1個(gè)4 096(212)、0個(gè)2 048(211)、0個(gè)1 024(210)、1個(gè)512(29)、1個(gè)256(28),以此類推,組成1001110100100。執(zhí)行與十進(jìn)制數(shù)相同的計(jì)算方法,可以寫成1×212 + 0×211 + 0×210 + 1×29 + 1×28 + 1×27 + 0×26 + 1×25 + 0×24 + 0×23 + 1×22 + 0×21 + 0×20。將表1-1中的十進(jìn)制數(shù)代入,得到4 096 + 512 + 256 + 128 + 32 + 4,等于5 028。
5 028是十進(jìn)制的四位數(shù)。在二進(jìn)制中,它是一個(gè)13位的數(shù)。
位數(shù)的多少?zèng)Q定了可以用十進(jìn)制表示的值的范圍。例如,在0~99的范圍內(nèi),可以用兩位數(shù)來(lái)表示。同樣,比特?cái)?shù)的多少也決定了可以用二進(jìn)制表示的值的范圍。例如,2比特可以表示0~3范圍內(nèi)的4個(gè)值。表1-2總結(jié)了可以用不同的比特?cái)?shù)來(lái)表示的值的數(shù)量和范圍。
表1-2 二進(jìn)制數(shù)的范圍

二進(jìn)制數(shù)中最右邊的位稱為最低有效位,最左邊的位稱為最高有效位,因?yàn)楦淖冏钣疫叺奈坏闹祵?duì)數(shù)值影響最小,而改變最左邊的位的值影響最大。計(jì)算機(jī)人喜歡用三個(gè)字母的縮寫,也就是我們常說(shuō)的TLA(Three-Letter Acronyms),最低有效位和最高有效位的縮寫通常為L(zhǎng)SB(Least Significant Bit)和MSB(Most Significant Bit)。圖1-7展示了用16位表示的數(shù)字5 028。

圖1-7 最低有效位(LSB)和最高有效位(MSB)
你會(huì)發(fā)現(xiàn),雖然5 028的二進(jìn)制表示法需要13位,但圖1-7顯示的是16位。就像在十進(jìn)制中在左側(cè)添加前導(dǎo)零一樣,我們總是可以使用比最低要求更多的容器。在十進(jìn)制中,05 028與5 028的值相同。二進(jìn)制數(shù)通常也是這樣表示的,因?yàn)橛?jì)算機(jī)是圍繞著位塊建立的。
1.5.2 二進(jìn)制加法
了解了如何用二進(jìn)制來(lái)表示數(shù)字,我們來(lái)看如何用二進(jìn)制數(shù)進(jìn)行簡(jiǎn)單的算術(shù)。在十進(jìn)制加法中,我們按照從右邊(最低有效位)到左邊(最高有效位)的順序把每個(gè)數(shù)字加起來(lái),如果結(jié)果大于9,則進(jìn)1位。類似地,我們按照從最低有效位至最高有效位的順序把二進(jìn)制數(shù)的每個(gè)位相加,如果結(jié)果大于1,則進(jìn)1位。
在二進(jìn)制中,加法實(shí)際上是比較簡(jiǎn)單的,因?yàn)?位的二進(jìn)制數(shù)只有4個(gè)可能的數(shù),而2位的十進(jìn)制數(shù)有100個(gè)數(shù)字。例如,圖1-8展示了如何用二進(jìn)制數(shù)進(jìn)行加法,把1和5相加,并顯示每一列上的進(jìn)位。

圖1-8 二進(jìn)制加法
數(shù)字1用二進(jìn)制表示是001,數(shù)字5是101,因?yàn)椋?×4)+(0×2)+(1×1)=5。把二進(jìn)制數(shù)字001和101相加,我們從最右列的最低有效位開始。這一列的二進(jìn)制數(shù)字1和1相加得2,而二進(jìn)制中并無(wú)表示2的符號(hào)。但是我們知道十進(jìn)制中的2實(shí)際上就是二進(jìn)制中的10([1×2]+[0×1]=2),所以我們把0作為和,把1進(jìn)到下一位。因?yàn)橹虚g的位都是0,所以只有之前進(jìn)位的1。然后,把最左列的數(shù)字相加:0加1就是二進(jìn)制中的1。最后的結(jié)果就是二進(jìn)制的110,或者說(shuō)是十進(jìn)制的6,也就是1和5相加后的結(jié)果。
你可能會(huì)注意到,二進(jìn)制加法的規(guī)則可以用前面討論過的邏輯運(yùn)算來(lái)表示,如圖1-9所示。我們將在第2章中看到,邏輯運(yùn)算實(shí)際上就是計(jì)算機(jī)硬件做二進(jìn)制加法的方式。

圖1-9 使用邏輯運(yùn)算的二進(jìn)制加法
當(dāng)我們把2個(gè)比特相加時(shí),計(jì)算結(jié)果的值是2個(gè)比特的XOR運(yùn)算結(jié)果,進(jìn)位的值是2個(gè)比特的AND運(yùn)算結(jié)果。從圖1-9中可以看到,二進(jìn)制中的1和1相加得到的結(jié)果是10。這意味著進(jìn)位值為1,也就是執(zhí)行表達(dá)式(1 AND 1)所得到的結(jié)果。同樣,表達(dá)式(1 XOR 1)會(huì)得到0,也就是給相加位本身分配的值。
將2個(gè)比特相加是一個(gè)很少單獨(dú)發(fā)生的操作。參照?qǐng)D1-8,我們似乎是把每列中的2個(gè)比特相加,但實(shí)際上是把3個(gè)比特相加,因?yàn)檫M(jìn)位也需參與加法運(yùn)算。幸運(yùn)的是,我們無(wú)須學(xué)習(xí)新的規(guī)則就可以把3個(gè)比特加在一起(因?yàn)楦鶕?jù)結(jié)合律,A+B+C等于(A+B)+C,所以可以用2次2個(gè)比特的加法把3個(gè)比特加在一起)。
當(dāng)加法的結(jié)果超出位數(shù)時(shí),會(huì)發(fā)生什么?這會(huì)導(dǎo)致溢出,只要從最高有效位進(jìn)位,就會(huì)出現(xiàn)溢出。例如,如果將4位二進(jìn)制數(shù)1001(910)與1000(810)相加,結(jié)果應(yīng)該是10001(1710),但最終會(huì)是0001(110),因?yàn)闆]有最高有效位的位置。我們將在后面詳細(xì)介紹計(jì)算機(jī)的條件碼寄存器,它是存放奇數(shù)信息的地方。其中一個(gè)是溢出位,它保存著最高有效位的進(jìn)位值。我們可以通過查看這個(gè)值來(lái)判斷是否發(fā)生溢出。
你可能知道,可以通過與一個(gè)數(shù)的負(fù)數(shù)相加來(lái)減去該數(shù)。下一節(jié)將介紹如何表示負(fù)數(shù)。向最高有效位借位稱為下溢。針對(duì)下溢,計(jì)算機(jī)也有對(duì)應(yīng)的條件碼。
1.5.3 表示負(fù)數(shù)
上一節(jié)中我們用二進(jìn)制表示的所有數(shù)字都是正數(shù),但現(xiàn)實(shí)世界中的很多問題都涉及正數(shù)和負(fù)數(shù),我們來(lái)看如何用二進(jìn)制來(lái)表示負(fù)數(shù)。例如,假設(shè)我們有4個(gè)比特可以使用。正如上一節(jié)提到的,4個(gè)比特可以表示0~15的16個(gè)數(shù)字。用4個(gè)比特可以表示16個(gè)數(shù)字并不意味著這些數(shù)字必須位于0~15。記住,語(yǔ)言是通過意義和語(yǔ)境來(lái)發(fā)揮作用的,這意味著我們可以設(shè)計(jì)出新的語(yǔ)境來(lái)解釋比特。
原碼表示法
符號(hào)常用來(lái)區(qū)分負(fù)數(shù)和正數(shù)。符號(hào)有兩個(gè)值,即正號(hào)和負(fù)號(hào),所以可以用比特來(lái)表示。我們將使用最左邊的位(MSB)來(lái)表示符號(hào),剩下的3個(gè)位可以表示0~7之間的數(shù)字。如果符號(hào)位為0,則將其視為正數(shù)。如果符號(hào)位為1,則將其視為負(fù)數(shù)。這樣,我們總共可以表示15個(gè)而不是16個(gè)不同的正數(shù)和負(fù)數(shù),因?yàn)檎?和負(fù)0是相等的。表1-3展示了使用此方法表示的–7~+7之間的數(shù)字。
表1-3 二進(jìn)制數(shù)的原碼表示

這就是所謂的原碼表示法,因?yàn)榧扔斜硎痉?hào)的位,也有表示大?。ɑ蛘哒f(shuō)這個(gè)值與零點(diǎn)的距離)的位。
原碼表示法并不常用,原因有兩個(gè):第一,位的建立需要成本,所以我們不想因?yàn)橛脙煞N不同的方法表示0而增加成本,我們更愿意用這個(gè)位組合來(lái)表示另一個(gè)數(shù)字;第二,使用XOR和AND的運(yùn)算不能用這種表示方法。
假設(shè)我們想把+1與–1相加。我們期望得到的結(jié)果是0,但使用原碼表示法,我們卻得不到該結(jié)果,如圖1-10所示。

圖1-10 使用原碼表示法的+1與–1的相加
可以看到,0001在二進(jìn)制中代表正數(shù)1,因?yàn)樗姆?hào)位是0;1001在二進(jìn)制中代表–1,因?yàn)樗姆?hào)位是1。用XOR和AND運(yùn)算把它們加在一起,就得到了1010。而1010表示十進(jìn)制的–2,顯然并非+1和–1之和。
雖然可以通過更復(fù)雜的邏輯來(lái)使原碼表示法在運(yùn)算中發(fā)揮作用,但盡可能地簡(jiǎn)化事情更有價(jià)值。我們需要探索一些其他的數(shù)字表示方法,以找到更好的方法。
反碼表示法
另一種得到負(fù)數(shù)的方法是取正數(shù)并翻轉(zhuǎn)它所有的位,這就是所謂的反碼表示法。我們以類似于原碼的方式對(duì)位進(jìn)行劃分。在這種情況下,用NOT運(yùn)算得到一個(gè)反碼。表1-4用反碼表示了–7~7的數(shù)字。
表1-4 二進(jìn)制數(shù)的反碼表示

可以看到,翻轉(zhuǎn)0111(+7)的每個(gè)位,可以得到1000(–7)。
反碼表示法仍然存在有兩個(gè)0的表示問題。它仍然不能讓我們?nèi)菀椎剡M(jìn)行加法運(yùn)算。為了解決這個(gè)問題,我們使用循環(huán)進(jìn)位,使最低有效位增加1,如果在最高有效位之外還有進(jìn)位,那我們就可以得到正確的結(jié)果。圖1-11說(shuō)明了它的原理。

圖1-11 反碼表示的二進(jìn)制數(shù)加法
要將用反碼表示的+2與–1相加,可以按正常情況對(duì)0010和1110進(jìn)行二進(jìn)制加法。因?yàn)樽罡哂行唬ǚ?hào)位)的數(shù)字相加結(jié)果是10,我們把0保留,將1作為循環(huán)進(jìn)位。但是我們只有4個(gè)位,所以當(dāng)?shù)竭_(dá)最高有效位時(shí),我們把進(jìn)位值帶回第一個(gè)位,得到0001,即+1,也就是+2和–1之和的正確值??梢钥吹?,這樣做過于復(fù)雜。
雖然這樣做是可行的,但它仍然不是一個(gè)很好的解決方案,因?yàn)檫@需要額外的硬件來(lái)添加循環(huán)進(jìn)位位。
現(xiàn)代計(jì)算機(jī)中既不使用原碼表示法,也不使用反碼表示法。沒有額外的硬件,使用這些方法進(jìn)行算術(shù)運(yùn)算是行不通的,而額外的硬件會(huì)增加成本。我們來(lái)看看能不能想出一個(gè)解決這個(gè)問題的方案。
補(bǔ)碼表示法
如果不添加任何特殊的硬件,只使用XOR和AND運(yùn)算,會(huì)有什么結(jié)果?讓我們想一想,當(dāng)與+1相加的時(shí)候,什么位型會(huì)計(jì)算得到0,并稱其為–1。如果我們堅(jiān)持用4個(gè)比特表示,+1就是0001。如圖1-12所示,1111與它相加,結(jié)果是0000,所以我們用這個(gè)位型來(lái)表示–1。

圖1-12 尋找–1
這就是所謂的補(bǔ)碼表示法,它是有符號(hào)整數(shù)最常用的二進(jìn)制表示法。對(duì)正數(shù)求反碼(即對(duì)每個(gè)位進(jìn)行NOT運(yùn)算),然后加1,舍棄MSB的任何進(jìn)位,就可以得到這個(gè)數(shù)字的負(fù)數(shù)。表示+1的0001的反碼是1110,加1就可以得到表示–1的1111。同理,+2是0010,它的反碼是1101,再加1就可以得到表示–2的1110。表1-5展示了用補(bǔ)碼表示的–8~7的數(shù)字。
表1-5 二進(jìn)制數(shù)的補(bǔ)碼表示

我們用0來(lái)試一下,看看補(bǔ)碼表示法是否解決了0的重復(fù)表示問題。取0000并翻轉(zhuǎn)每個(gè)位,我們得到它的反碼1111。將1加到1111,得到[1]0000,但由于這是一個(gè)5位的數(shù)字,超過了我們可用的位數(shù),所以我們可以忽略掉進(jìn)位位中的1。這樣就只剩下0000,也就是剛開始的0,所以0在補(bǔ)碼表示法中只有一個(gè)表示形式。
程序員得知道需要多少位來(lái)表示他們需要的數(shù)字。對(duì)于這些內(nèi)容,程序員們應(yīng)爛熟于心。你可以參考表1-6,該表給出了不同位數(shù)下可以用補(bǔ)碼表示的數(shù)字范圍。
表1-6 補(bǔ)碼表示的數(shù)字范圍

從表1-6可以看出,隨著位數(shù)的增加,可以表示的數(shù)字范圍呈指數(shù)級(jí)增加。重要的是要記住,我們總是需要根據(jù)語(yǔ)境來(lái)確定我們看到的4位數(shù)字1111是15,而不是用補(bǔ)碼表示的–1或用原碼表示的–7,也不是用反碼表示的–0。你必須知道使用的是哪種表示方式。
- iOS面試一戰(zhàn)到底
- 玩轉(zhuǎn)Scratch少兒趣味編程
- FuelPHP Application Development Blueprints
- SoapUI Cookbook
- Java高并發(fā)核心編程(卷2):多線程、鎖、JMM、JUC、高并發(fā)設(shè)計(jì)模式
- Responsive Web Design with HTML5 and CSS3
- 精通軟件性能測(cè)試與LoadRunner實(shí)戰(zhàn)(第2版)
- Learning Laravel 4 Application Development
- STM32F0實(shí)戰(zhàn):基于HAL庫(kù)開發(fā)
- Backbone.js Blueprints
- PHP 7+MySQL 8動(dòng)態(tài)網(wǎng)站開發(fā)從入門到精通(視頻教學(xué)版)
- Go語(yǔ)言編程
- 微課學(xué)人工智能Python編程
- Python硬件編程實(shí)戰(zhàn)
- Java EE實(shí)用教程