前言
最近在看《Computer System: A Programmer's Perspective》纸兔,學(xué)會了很多基礎(chǔ)性的知識妒峦,于是總結(jié)出來與大家分享幅疼。
位與二進(jìn)制
在現(xiàn)實生活中背捌,我們會用紙和筆來記錄數(shù)據(jù)癌刽,比如在之前智能手機還沒有普及的年代力图,還有相當(dāng)一部分人使用小本本來記錄電話號碼步绸,顯然電話號碼作為一種數(shù)據(jù),記錄在紙上搪哪。
那么在計算機中是如何記錄數(shù)據(jù)靡努,表達(dá)信息的呢坪圾?
計算機使用一個序列的位(bit)來記錄數(shù)據(jù)。
一個位中存儲著一個二進(jìn)制數(shù)字惑朦,什么是二進(jìn)制呢兽泄?二進(jìn)制簡單來說就是逢二進(jìn)位的一種進(jìn)制,人類最常用漾月,最直觀的一直使用著十進(jìn)制病梢,可用的符號為:0,1梁肿,2蜓陌,3,4吩蔑,5钮热,6,7烛芬,8隧期,9,總共有10個符號赘娄,二進(jìn)制則只需要兩個符號0和1仆潮。對機器來說,使用二進(jìn)制是非常方便的一種形式遣臼,因為二進(jìn)制只需要兩種符號性置,也就是說,機器只需要能夠維持兩種不同的狀態(tài)來對應(yīng)這兩種符號即可揍堰,比如高電壓和低電壓鹏浅,通電和斷電等等。所以屏歹,對計算機來說篡石,使用二進(jìn)制(維持兩個狀態(tài))是非常有效的方式。
計算機使用一連串的位來記錄數(shù)據(jù)西采,比如1位,那么這個位上只能表示0或1继控,通常1位的數(shù)據(jù)幾乎沒有什么作用械馆。在現(xiàn)在的計算機中,使用8個位來作為一個基本的單位武通,稱為字節(jié)(byte)霹崎,那么它寫出來應(yīng)該是這樣的:
//8個位都是0,對應(yīng)十進(jìn)制數(shù)字0冶忱,中間空開一個空格尾菇,四位四位的寫在一起是為了可讀性
0000 0000
//右側(cè)的一般稱為最低位,左側(cè)的稱為最高位,最低位加1派诬,對應(yīng)十進(jìn)制中的1
0000 0001
//最低位繼續(xù)加1劳淆,1+1=2,因為是二進(jìn)制默赂,必須進(jìn)位了沛鸵,對應(yīng)十進(jìn)制數(shù)字2
0000 0010
0000 0011
...
//8位二進(jìn)制最大值,對應(yīng)十進(jìn)制2^8-1=255
1111 1111
以上就是一串從0開始遞增的二進(jìn)制序列缆八。
十六進(jìn)制
剛剛說完了二進(jìn)制數(shù)曲掰,現(xiàn)在簡單介紹一下十六進(jìn)制數(shù),二進(jìn)制數(shù)與十六進(jìn)制數(shù)之間有非常巧妙的關(guān)系奈辰。
十進(jìn)制需要10個符號:0, 1, 2, 3, 4, 5, 6, 7, 8, 9
二進(jìn)制需要2個符號:0, 1
十六進(jìn)制需要16個符號:0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
其中栏妖,a-f分別對應(yīng)著十進(jìn)制中的10, 11, 12, 13, 14, 15
十六進(jìn)制數(shù)值中的英文字母是不區(qū)分大小寫的。
現(xiàn)在奖恰,讓我們來考慮4位二進(jìn)制數(shù)
0000(2) -> 0(10) -> 0(16) //括號中表示進(jìn)制
一個4位的二進(jìn)制數(shù)最小為0000
吊趾,也就是十進(jìn)制中的0
,也是十六進(jìn)制的0
房官。那么4位二進(jìn)制最大的值為1111
趾徽,那么它的值為
也就是說,四位二進(jìn)制能表示數(shù)的范圍區(qū)間為[0, 15]翰守,這剛好是1位16進(jìn)制數(shù)所能表示的數(shù)值范圍孵奶。
四位二進(jìn)制數(shù)能搞好使用一位十六進(jìn)制數(shù)來表示。
于是蜡峰,當(dāng)計算機中的數(shù)值使用二進(jìn)制表示時了袁,通常會出現(xiàn)成片成片的010101……,這看起來非常頭疼湿颅,非常容易讓人看錯载绿,但是,我們可以從低位開始油航,四個四個的表示為十六進(jìn)制數(shù)崭庸。如下所示:
0000 0000(2) -> 00(16)
0000 1111(2) -> 0x0f //通常,"0x"前綴用來指明是一個十六進(jìn)制數(shù)
1111 0001(2) -> 0xF1 //十六進(jìn)制的字母是不區(qū)分大小寫的谊囚,注意這里大寫的F
100 1111(2) -> 0x4f //注意怕享!這里的二進(jìn)制數(shù)只有7位,通常我們從最低位開始轉(zhuǎn)換成十六進(jìn)制數(shù)镰踏,高位在沒有指明的情況下函筋,使用0補齊
0100 1111(2) -> 0x4f //上一行的二進(jìn)制數(shù)高位補齊0的情況
有了十六進(jìn)制,我們就可以方便簡潔的表示非常多位的二進(jìn)制數(shù)奠伪,有了更高的可讀性跌帐。
整數(shù)
整數(shù)又分為有符號與無符號的區(qū)別首懈,無符號整數(shù)即是大于等于0的整數(shù)。
無符號整數(shù)的表示
無符號整數(shù)的表示是基于最原始的二進(jìn)制表示數(shù)據(jù)的辦法谨敛,也就是說究履,給定n位二進(jìn)制序列,它所能表示的數(shù)值范圍是佣盒。
是怎么來的呢挎袜,其實很簡單,比如我們給出一個字節(jié)(8位)來表示一個整數(shù)肥惭,0000 0000
盯仪,它能表示的最小值應(yīng)該是0了,也就是8位全是0蜜葱,那么最大值呢全景?當(dāng)然是8位全是1了,也就是1111 1111
牵囤,現(xiàn)在不妨給它加個1爸黄,那么它會變成9位的二進(jìn)制數(shù)值1 0000 000
,此時揭鳞,這個9位的二進(jìn)制數(shù)的值為炕贵,那么8位最大值當(dāng)然就是了。
所以野崇,無符號整數(shù)在計算機中的表示称开,就是簡單的對位序列的數(shù)值理解即可。
有符號整數(shù)的表示
通過位序列來表示有符號整數(shù)是非常巧妙的乓梨,稱之為補碼編碼的方式來表示有符號整數(shù)鳖轰。所謂使用補碼來表示有符號數(shù),實際上對二進(jìn)制序列并沒有什么特別大的處理扶镀,它仍然是010101……這樣的一串東西蕴侣,只是我們用一套稱為“補碼”的規(guī)則來理解這串位序列而已。
補碼就是將位序列的最高位解釋為負(fù)權(quán)臭觉。
比如昆雀,給定位序列,一個字節(jié)1000 0001
蝠筑,如果它是表示無符號整數(shù)忆肾,它的值是多少呢?很簡單菱肖,那么如果我們用補碼來理解它呢?最高位是負(fù)權(quán)旭从,也就是稳强。
補碼會將最高位理解為一個負(fù)數(shù)场仲,直觀點來看,就是當(dāng)最高位是1的時候退疫,是一個非常大的負(fù)數(shù)加上后面的正整數(shù)渠缕,后面的正整數(shù)越大,這個負(fù)數(shù)越小褒繁,越靠近0亦鳞,當(dāng)最高位是0的時候,那么負(fù)權(quán)整個都是0棒坏,剩下的幾位如同無符號整數(shù)一樣表示正整數(shù)燕差。
我們用一串遞增的序列來理解。
0000 0000 -> -0 + 0 = 0
0000 0001 -> -0 + 1 = 1
...
0111 1110 -> -0 + 126 = 126
0111 1111 -> -0 + 127 = 127
1000 0000 -> -128 + 0 = -128
1000 0001 -> -128 + 1 = -127
1000 0010 -> -128 + 2 = -126
...
1111 1110 -> -128 + 126 = -2
1111 1111 -> -128 + 127 = -1
通過上述遞增的序列坝冕,你會發(fā)現(xiàn)徒探,8位二進(jìn)制能表示的有符號整數(shù)的范圍是,我們可以用數(shù)學(xué)的語言來描述一個n位的二進(jìn)制能表示的有符號整數(shù)范圍是喂窟。
有符號數(shù)與無符號數(shù)的相互轉(zhuǎn)換
通常在高級程序語言中测暗,有無符號整數(shù)的相互轉(zhuǎn)換是不改變位序列的,只是換了一種“解析”方式去解釋位序列磨澡,比如說1000 0000
是一個無符號數(shù)碗啄,那么它的值是128
,如果我們把它轉(zhuǎn)換成一個有符號數(shù)稳摄,位序列不變稚字,只是用補碼的方式去理解它,那么它的數(shù)值就變成了-128
了秩命。
我來用一張圖片說明得更清楚一些尉共。
如上圖所示,在數(shù)據(jù)大小為8位的情況下弃锐,它的低7位所表示的數(shù)值范圍
在我們寫代碼的時候一定要注意這一點剧蚣。
擴展一個數(shù)值的位
如果我們要將一個8位的數(shù)據(jù)放到16位的容器中去,那么我們需要對數(shù)據(jù)進(jìn)行擴展旋廷, 也就是我們需要確定新的高8位應(yīng)該是放0還是放1鸠按。
這個問題很簡單,對于無符號數(shù)的擴展饶碘,只要簡單的在高位上補充滿0即可目尖,這種方式稱為零擴展。對于有符號數(shù)扎运,只需要在高位補充滿1即可瑟曲,這種方式稱為符號擴展饮戳。
整數(shù)的加法計算
整數(shù)的計算分為,無符號整數(shù)加法洞拨,有符號整數(shù)加法扯罐,無符號與有符號整數(shù)混合的加法。
對于無符號整數(shù)的計算烦衣,只是單單從位級上考慮就行了歹河,但是當(dāng)兩個二進(jìn)制數(shù)相加后,最高位向前進(jìn)1了花吟,那么就會產(chǎn)生溢出秸歧,本來應(yīng)該變成一個更大的數(shù),結(jié)果卻變小了示辈。
對于有符號數(shù)的計算寥茫,同樣的也是從位級上進(jìn)行加法計算,一樣的矾麻,最高位如果向前進(jìn)一了纱耻,一樣會產(chǎn)生溢出。比如對于8位的數(shù)據(jù)险耀,-128 - 128 -> 0
弄喘,我們在位級上進(jìn)行考慮
//這是一個豎式
1000 0000
1000 0000
----------
0000 0000
對于有符號數(shù)與無符號數(shù)混合的表達(dá)式,一般需要查看編譯器是如何處理這個問題的甩牺,有可能是將有符號數(shù)轉(zhuǎn)成無符號數(shù)再進(jìn)行計算蘑志,也有可能是將無符號數(shù)轉(zhuǎn)成有符號數(shù)再進(jìn)行計算。這個問題跟整型與浮點數(shù)相加是類似的問題贬派,還是看編譯器/虛擬機是具體如何解決這個問題的急但。
浮點數(shù)
前面所說的有符號整數(shù)與無符號整數(shù),都屬于定點數(shù)搞乏,就是小數(shù)點固定的數(shù)波桩,而浮點數(shù),即小數(shù)點是可以浮動(變化)的數(shù)请敦。自從我聽到浮點數(shù)這個概念镐躲,在一個很長的時間里,我都以為浮點數(shù)就是指小數(shù)侍筛,其實不是這樣的萤皂,浮點數(shù)并不是狹隘的說一定要表示為小數(shù)(如123.45),應(yīng)該更準(zhǔn)確的理解為小數(shù)點的位置不是固定的匣椰,也就是說裆熙,這種數(shù)的“表示/解析”方法是可以表示小數(shù)點在不同位置的數(shù)的。
二進(jìn)制小數(shù)
在解釋浮點數(shù)之前,我們先要知道一下二進(jìn)制的小數(shù)是什么情況齐媒。
先看一個二進(jìn)制整數(shù)的例子,如101
纷跛,那么它的十進(jìn)制數(shù)值為,那么邀杏,當(dāng)二進(jìn)制數(shù)值有小數(shù)點時贫奠,101.101
,它的十進(jìn)制數(shù)值為
可以發(fā)現(xiàn)望蜡,0.1(2)唤崭,0.01(2),0.001(2)……二進(jìn)制小數(shù)每一位能表示為脖律,谢肾,……因此它要表示某一個十進(jìn)制的小數(shù),需要用這些部分累加在一起實現(xiàn)小泉。
IEEE浮點標(biāo)準(zhǔn)
在生產(chǎn)環(huán)境中芦疏,一個基本的浮點數(shù)都是32位的,不會像上文中一直使用的8位來當(dāng)數(shù)據(jù)的大小微姊,IEEE浮點標(biāo)準(zhǔn)中就規(guī)定了這32位應(yīng)該如何使用酸茴。
它將位序列分為三個部分來理解,
第一部分:符號兢交,決定這個數(shù)是正數(shù)還是負(fù)數(shù)薪捍,一般用1表示負(fù)數(shù),0表示正數(shù)配喳。
第二部分:尾數(shù)酪穿,是一個二進(jìn)制小數(shù)。
第三部分:階碼晴裹,是對浮點數(shù)的加權(quán)被济。
可以這樣去理解,有點像科學(xué)計數(shù)法息拜,比如:
100溉潭,我們可以記為,
0.257少欺,我們可以記為
257喳瓣,可以記為
IEEE標(biāo)準(zhǔn)中,給定了32位與64位浮點數(shù)赞别,各個部分的格式畏陕。
32位浮點數(shù):
最高位:符號位(1位)-階碼(8位)-尾數(shù)(23位):最低位
64位浮點數(shù):
最高位:符號位(1位)-階碼(11位)-尾數(shù)(52位):最低位
我們先假設(shè)一個8位的浮點數(shù),并通過將它的數(shù)值列在一個表格中來觀察學(xué)習(xí)浮點數(shù)的標(biāo)準(zhǔn)是如何工作的仿滔。
8位浮點數(shù)惠毁,我們設(shè)定最高的1位是符號位犹芹,0表示正數(shù),1表示負(fù)數(shù)鞠绰,接下來的4位表示階碼腰埂,最低3位表示尾數(shù)。請看下表蜈膨。
上表屿笼,只列出了符號位是0的情況。
規(guī)格化數(shù)與非規(guī)格化數(shù)
我們注意A列的描述翁巍,浮點數(shù)大體上分為三種情況驴一,非規(guī)格化數(shù),規(guī)格化數(shù)灶壶,其他值
非規(guī)格化數(shù)的特征是肝断,階碼段的位都是0。
規(guī)格化數(shù)的特征是驰凛,階碼段不全為0胸懈,也不全為1。
其他值的特征就是洒嗤,階碼段全為1箫荡。
指數(shù)部分
然后,我們注意一下D列渔隶,有一個偏置值的概念羔挡,它的值是,k的值是階碼的長度(位寬)间唉,在我們自定義的8-bit浮點數(shù)中绞灼,k的值為4,所以偏置值是7呈野。
階碼E是分兩種情況的低矮。
當(dāng)這個浮點數(shù)是非規(guī)格化數(shù)的時候,
當(dāng)這個浮點數(shù)是規(guī)格化數(shù)的時候被冒,
e是階碼段的4-bit位序列所表示的無符號數(shù)军掂。
所以,表格中E列指的是4-bit位序列按無符號數(shù)解析的十進(jìn)制無符號數(shù)昨悼。
而F列蝗锥,則是按是否是規(guī)格化數(shù)來決定的值。這個偏置值的設(shè)定與補碼負(fù)權(quán)的設(shè)計是非常相似的率触。
最終指數(shù)部分就是了终议。
小數(shù)部分
小數(shù)部分M是由最后三位決定的,它同樣是分情況的。
當(dāng)這個浮點數(shù)是非規(guī)格化數(shù)的時候穴张,位序列BBB應(yīng)當(dāng)理解為0.BBB细燎,也就是整數(shù)部分為0的二進(jìn)制小數(shù)。
當(dāng)這個浮點數(shù)是規(guī)格化數(shù)的時候皂甘,位序列BBB應(yīng)當(dāng)理解為1.BBB玻驻,也就是整數(shù)部分為1的二進(jìn)制小數(shù)。
此時偿枕,對照表格的H列與I列击狮,即可明白它們的含義。
浮點數(shù)的值
最終益老,這個浮點數(shù)的值就這樣得出來了。sign是第一位決定符號的寸莫。
抽象數(shù)據(jù)模型(Abstract Data Models)
最近無意間看到一個這樣的概念捺萌,與計算機中的數(shù)有關(guān),就在這里提及下膘茎。
應(yīng)用與操作系統(tǒng)都有一個抽象數(shù)據(jù)模型桃纯,大部分應(yīng)用都沒有顯式的表現(xiàn)出這個模型,但是它會影響到代碼的編寫披坏,在32 bits programming model(ILP32)上态坦,integer, long, pointer都是32 bits的,大部分開發(fā)者都沒有意識到這一點棒拂。
現(xiàn)在系統(tǒng)擴展到64 bits伞梯,如果把所有的數(shù)據(jù)類型都擴展到64位是非常浪費的,因為很多應(yīng)用并不需要真的用到64位那么大的數(shù)據(jù)格式帚屉,但是pointer卻需要擴展到64位谜诫,所以在LLP64/P64上,pointer被擴展到64位攻旦,其他的仍然保持32位喻旷。
以上內(nèi)容翻譯自Abstract Data Models
抽象數(shù)據(jù)模型指定了編程語言中幾個基礎(chǔ)數(shù)據(jù)類型的大小。
比如LP64(可能是64-bit Leopard的縮寫)是使用在64位OSX系統(tǒng)或者Linux系統(tǒng)上的牢屋,它指定了integer為32位且预,long是64位,pointer是64位烙无。
還有LLP64锋谐,這是windows 64位操作系統(tǒng)所選擇的ADM,它的integer/long/pointer分別使用的是32/32/64位皱炉。
更細(xì)致的說明與討論怀估,我已經(jīng)整理好了參考資料給大家。
參考資料
- 《64-bit data models》wiki上的解釋。
- 《Abstract Data Models》這篇文檔介紹了Abstract Data Model這個概念多搀,提及了ILP32與Win64用的LLP64歧蕉。
- 《Windows Data Types》
- 《The New Data Types》這兩篇文檔列舉了數(shù)據(jù)類型的大小。
- 《64-Bit Programming Models: Why LP64?》這篇文檔詳細(xì)比較康铭,討論幾種不同的抽象數(shù)據(jù)模型惯退。
- 《深入理解計算機系統(tǒng)》
有看不懂的地方請給我說,我再添加更詳細(xì)的解釋从藤;有講得不正確的地方還歡迎大家指正與討論:D