官术网_书友最值得收藏!

第一篇 x86基礎(chǔ)

這一篇探討x86架構(gòu)的基礎(chǔ)平臺(tái)知識(shí),有如下7章。

第1章 數(shù)與數(shù)據(jù)類型

第2章 x86/x64編程基礎(chǔ)

第3章 編寫(xiě)本書(shū)的實(shí)驗(yàn)例子

第4章 處理器的身份

第5章 了解Flags

第6章 處理器的控制寄存器

第7章 MSR

第3章很重要,講解如何編寫(xiě)、生成和運(yùn)行本書(shū)的所有實(shí)驗(yàn)例子。在繼續(xù)本書(shū)后續(xù)章節(jié)前應(yīng)好好了解這一章里的內(nèi)容。

第1章 數(shù)與數(shù)據(jù)類型

我們知道在計(jì)算機(jī)中處理的數(shù)是按照一定的規(guī)則進(jìn)行組織和存放的。其中的每個(gè)數(shù)按特定的編碼規(guī)則組織。可是光有這些數(shù)的組織規(guī)則還是不夠,計(jì)算機(jī)每條指令的操作數(shù)可能會(huì)有不同的數(shù)據(jù)類型。那么計(jì)算機(jī)能處理哪些數(shù)據(jù)類型呢?在這一章里,我們將要了解數(shù)與數(shù)據(jù)類型。

1.1 數(shù)

計(jì)算機(jī)能處理各種各樣的信息,計(jì)算機(jī)硬件對(duì)數(shù)據(jù)進(jìn)行處理后,可呈現(xiàn)出各種各樣的信息。

1.1.1 數(shù)字

數(shù)字是個(gè)基本的計(jì)數(shù)符號(hào)。通用的數(shù)字有10個(gè):0,1,2,3,4,5,6,7,8,9。以這些數(shù)字組合構(gòu)成的數(shù)是十進(jìn)制數(shù)。

思考各個(gè)進(jìn)制數(shù)的數(shù)字。

1.二進(jìn)制數(shù)字

包括0和1。

2.八進(jìn)制數(shù)字

包括0,1,2,3,4,5,6,7。

3.十進(jìn)制數(shù)字

包括0,1,2,3,4,5,6,7,8,9。

4.十六進(jìn)制數(shù)字

包括0,1,2,3,4,5,6,7,8,9及字母A,B,C,D,E,F(xiàn)。

各個(gè)進(jìn)制以相應(yīng)的數(shù)字表達(dá)的計(jì)數(shù)范圍作為base值,如:二進(jìn)制的base值是2,八進(jìn)制的base值是8,十進(jìn)制的base值是10,十六進(jìn)制的base值是16。

1.1.2 二進(jìn)制數(shù)

二進(jìn)制數(shù)是計(jì)算機(jī)運(yùn)算的基礎(chǔ),無(wú)論何種制式的數(shù),在計(jì)算機(jī)中都是以二進(jìn)制形式存放的。由二進(jìn)制數(shù)字組成的數(shù)字序列是二進(jìn)制數(shù),如下所示。

二進(jìn)制數(shù)組合里,每個(gè)數(shù)位被稱為bit(位),能表達(dá)值0和1。二進(jìn)制數(shù)的base值是2,那么在n個(gè)二進(jìn)制數(shù)字的序列中,其值為

值=(Dn-1×2n-1)+(Dn-2×2n-2)+…+(D1×21)+(D0×20

這是一個(gè)數(shù)學(xué)上的算式。這個(gè)值是我們很容易辨識(shí)的十進(jìn)制值。

1.1.3 二進(jìn)制數(shù)的排列

在日常的書(shū)寫(xiě)或表達(dá)上,最左邊的位是最高位。數(shù)的位排列從左到右,對(duì)應(yīng)的值從高到低。可是在機(jī)器的數(shù)字電路上,數(shù)的高低位可以從左到右進(jìn)行排列,也可以從右到左進(jìn)行排列。這樣就產(chǎn)生了MSB和LSB的概念。

什么是MSB?什么是LSB?

以一個(gè)自然的二進(jìn)制表達(dá)序列上32位的二進(jìn)制數(shù)為例,最右邊是bit 0,最左邊是bit 31。那么bit 0就用LSB(Least Significant Bit,最低有效位)來(lái)表示,bit 31就用MSB(Most Significant Bit,最高有效位)來(lái)表示。

MSB也用做符號(hào)位(1為負(fù),0為正),但若在無(wú)符號(hào)數(shù)上,則MSB就是數(shù)的最高位,LSB是數(shù)的最低位。無(wú)論一個(gè)數(shù)在機(jī)器上是從左到右排列,還是從右到左排列,使用MSB和LSB的概念都很容易對(duì)其二進(jìn)制形式進(jìn)行描述說(shuō)明。

小端序與大端序

二進(jìn)制數(shù)在計(jì)算機(jī)的組織存放中,地址由低位到高位對(duì)應(yīng)著兩種排列。

① 由LSB到MSB,這就是小端序(little-endian)排法。

② 由MSB到LSB,這就是大端序(big-endian)排法。

在x86/x64體系中使用的是小端序存儲(chǔ)格式,也就是:MSB對(duì)應(yīng)著存儲(chǔ)器地址的高位,LSB對(duì)應(yīng)著存儲(chǔ)器地址的低位。

在有些RISC(精簡(jiǎn)指令集計(jì)算機(jī))體系里,典型的如Power/PowerPC系列,使用大端序排法。即在由低到高的地址位里,依次存放MSB到LSB。亦即:MSB存放在存儲(chǔ)器地址的低位,LSB存放在高位。

代碼清單1-1:

mov dword [Foo],1
test byte [Foo],1                                ; 測(cè)試 LSB 是否存放在低端上
jnz IS_little_endian                              ; 是小端序

上面的代碼將1存放在32位的內(nèi)存里,通過(guò)讀取內(nèi)存的低字節(jié)來(lái)判斷1到底存放在低字節(jié)還是高字節(jié),從而區(qū)分是小端序還是大端序。

某些RISC機(jī)器上是可以在大端序與小端序存儲(chǔ)序列之間做選擇的。大端序格式看上去更符合人類表達(dá)習(xí)慣,而小端序看上去不那么直觀,不過(guò)這對(duì)于計(jì)算機(jī)的處理邏輯并無(wú)影響。

實(shí)驗(yàn)1-1:測(cè)試字節(jié)內(nèi)的位排列

字節(jié)內(nèi)的位是否有大端序和小端序之分?這似乎沒(méi)有定論,我們不是硬件設(shè)計(jì)人員,很難做出判斷。筆者傾向于認(rèn)為位的排列是區(qū)分的。

從代碼清單1-1我們可以測(cè)試機(jī)器是屬于小端序還是大端序,原理是根據(jù)字節(jié)在內(nèi)存中的存儲(chǔ)序列進(jìn)行判斷。對(duì)代碼稍做修改,即可用來(lái)測(cè)試位的排列,如代碼清單1-2所示。

代碼清單1-2(topic01\ex1-1\boot.asm):

      mov dword [Foo],2                                       ;
00000000000000000000000000000010B
      bt dword [Foo],1                                        ; 取 bit 1
      setc bl                                                     ; bit 1 是否等于 1
      movzx ebx,bl
      mov si,[message_table + ebx * 2]
      call print_message
next:
      jmp $
Foo                     dd 0
LSB_to_MSB            db 'byte order:LSB to MSB',13,10,0
MSB_to_LSB            db 'byte order:MSB to LSB',13,10,0
message_table        dw MSB_to_LSB,LSB_to_MSB,0

代碼清單1-2中測(cè)試內(nèi)存中的bit 1是否為存進(jìn)去的值1,然后輸出一條信息。下面是這個(gè)實(shí)驗(yàn)在真實(shí)計(jì)算機(jī)上的測(cè)試結(jié)果。

實(shí)際上這個(gè)方法未必能測(cè)出什么(如果CPU一次訪問(wèn)字節(jié),這個(gè)測(cè)試結(jié)果并不能說(shuō)明什么)。按這個(gè)方法測(cè)出字節(jié)內(nèi)也是小端序排列的。

完整的測(cè)試代碼在topic01\ex1-1\目錄里。關(guān)于如何在真機(jī)上進(jìn)行測(cè)試,請(qǐng)看第3章。

1.1.4 十六進(jìn)制數(shù)

在二進(jìn)制數(shù)中,每一個(gè)數(shù)位只能表達(dá)兩個(gè)值。如果要表達(dá)一個(gè)很大的數(shù),就將比較麻煩。而十六進(jìn)制數(shù)能在更短的序列里表達(dá)更大的數(shù)值范圍。

十六進(jìn)制數(shù)由十六進(jìn)制數(shù)字組成,每個(gè)數(shù)位能表達(dá)16個(gè)值:從0到15。

超過(guò)9的計(jì)數(shù)用字母來(lái)代替,A~F分別表示10~15,它的base值就是16,同樣由以下的計(jì)算式子來(lái)求十六進(jìn)制的值:

值=(Dn-1×16n-1)+(Dn-2×16n-2)+…+(D1×161)+(D0×160

一個(gè)十六進(jìn)制數(shù)字能表達(dá)4個(gè)二進(jìn)制數(shù)字。與二進(jìn)制數(shù)相比,采用十六進(jìn)制數(shù)進(jìn)行計(jì)數(shù)大大方便了書(shū)寫(xiě),也方便了閱讀。

1.1.5 八進(jìn)制數(shù)與十進(jìn)制數(shù)

同樣,八進(jìn)制數(shù)由八進(jìn)制數(shù)字組成,十進(jìn)制數(shù)由十進(jìn)制數(shù)字組成。我們的日常生活中常使用十進(jìn)制數(shù)。而二進(jìn)制數(shù)、八進(jìn)制數(shù)及十六進(jìn)制數(shù)表達(dá)的數(shù),并不被我們直觀地識(shí)別。

為了書(shū)寫(xiě)及識(shí)別方便,會(huì)使用前綴或者后綴對(duì)數(shù)進(jìn)行修飾。

使用后綴:d(D)或默認(rèn)描述為十進(jìn)制數(shù),b(B)為二進(jìn)制數(shù),h(H)為十六進(jìn)制數(shù),o(O)為八進(jìn)制數(shù)。

上面是常用的修飾方式,在C語(yǔ)言中對(duì)十六進(jìn)制數(shù)常使用前綴0x進(jìn)行修飾,由于C語(yǔ)言的影響力現(xiàn)在使用0x前綴很通用。

在nasm語(yǔ)言里數(shù)的修飾更多了,下面是nasm的里例子。

mov     ax,200          ; decimal
mov     ax,0200         ; still decimal
mov     ax,0200d        ; explicitly decimal
mov     ax,0d200        ; also decimal
mov     ax,0c8h         ; hex
mov     ax,$0c8         ; hex again:the 0 is required
mov     ax,0xc8         ; hex yet again
mov     ax,0hc8         ; still hex
mov     ax,310q         ; octal
mov     ax,310o         ; octal again
mov     ax,0o310        ; octal yet again
mov     ax,0q310        ; octal yet again
mov     ax,11001000b    ; binary
mov     ax,1100_1000b   ; same binary constant
mov     ax,1100_1000y   ; same binary constant once more
mov     ax,0b1100_1000  ; same binary constant yet again
mov     ax,0y1100_1000  ; same binary constant yet again

1.2 數(shù)據(jù)類型

在x86/x64體系中,指令處理的數(shù)據(jù)分為fundamental(基礎(chǔ))和numeric(數(shù)值)兩大類。基礎(chǔ)類型包括:byte(8位),word(16位),doubleword(32位),以及quadword(64位),它們代表指令能一次性處理的數(shù)據(jù)寬度。

numeric數(shù)據(jù)類型使用在運(yùn)算類指令上,總結(jié)來(lái)說(shuō)x86/x64體系的運(yùn)算類指令能處理下面四大類數(shù)據(jù)。

① integer(整型數(shù)):包括unsigned類型和singed類型。

② floating-point(浮點(diǎn)數(shù)):包括single-precision floating-point(單精度浮點(diǎn)數(shù)),double-precision floating-point(雙精度浮點(diǎn)數(shù)),以及double extended-precision floatingpoint(擴(kuò)展雙精度浮點(diǎn)數(shù))。

③ BCD(binary-code decmial integer):包括non-packed BCD碼和packed-BCD碼。

④ SIMD(single instruction,multiple data):這是屬于packed類型的數(shù)據(jù)。

SIMD數(shù)據(jù)是在一個(gè)operand(操作數(shù))里集成了多個(gè)integer、floating-point或者BCD數(shù)據(jù)。SIMD指令可以一性次同時(shí)處理這些數(shù)據(jù)。

1.2.1 integer數(shù)

在計(jì)算機(jī)處理中,整數(shù)會(huì)區(qū)分signed(有符號(hào)數(shù))和unsigned(無(wú)符號(hào)數(shù))兩種情況,數(shù)值的MSB值被作為符號(hào)位。每個(gè)數(shù)值類型有自己的取值范圍,如下所示。

可是在計(jì)算機(jī)中根本無(wú)法判斷一個(gè)整數(shù)是signed數(shù)還是unsigned數(shù)。例如0ABh這個(gè)整數(shù)就無(wú)法知道它是signed數(shù)還是unsigned數(shù)。

計(jì)算機(jī)能做到的是:在整數(shù)的使用中,在應(yīng)該使用signed數(shù)的場(chǎng)合下認(rèn)為它是signed數(shù),而在使用unsigned數(shù)的場(chǎng)合下認(rèn)為它是unsigned數(shù)。

在這種假定下,即使不是signed數(shù)也會(huì)被當(dāng)做signed數(shù)進(jìn)行處理。既然這樣,在計(jì)算機(jī)運(yùn)算中就無(wú)須判斷是signed數(shù)還是unsigned數(shù),只需假定它是signed數(shù)或是unsigned數(shù)。

而在浮點(diǎn)數(shù)上,每個(gè)浮點(diǎn)數(shù)都有符號(hào)位,因此浮點(diǎn)數(shù)能夠清楚地識(shí)別它就是signed數(shù)。所以浮點(diǎn)數(shù)不存在unsigned數(shù)的情況。

在x86機(jī)器上,對(duì)整數(shù)的加減法運(yùn)算過(guò)程中不會(huì)識(shí)別signed數(shù)與unsigned數(shù),而根據(jù)signed與unsigned兩種運(yùn)算結(jié)果進(jìn)行相應(yīng)的eflags標(biāo)志位設(shè)置。

代碼清單1-3:

mov eax,0x70000000
mov ebx,0x80000000
sub eax,ebx

上面的代碼中,0x70000000和0x80000000是signed數(shù)還是unsigned數(shù)呢?

二進(jìn)制運(yùn)算結(jié)果值是0xF0000000,指令會(huì)同時(shí)對(duì)結(jié)果進(jìn)行兩種分析設(shè)置。

為signed時(shí)

假定運(yùn)算雙方是signed數(shù)時(shí),這個(gè)結(jié)果是錯(cuò)誤的,它產(chǎn)生了溢出。它會(huì)置eflags寄存器的OF(Overflow Flag)標(biāo)志為1,以及SF(Sign Flag)標(biāo)志為1,表示結(jié)果為負(fù)數(shù)。

為unsigned時(shí)

假定雙方是unsigned數(shù)時(shí),它會(huì)置CF(Carry Flag)標(biāo)志為1,表示產(chǎn)生了借位。

因此:這條指令會(huì)同時(shí)對(duì)OF、SF及CF標(biāo)志置位。而對(duì)這個(gè)結(jié)果如何運(yùn)用那是程序員的職責(zé)。

另外,RISC體系的機(jī)器普遍會(huì)在指令層上做假定運(yùn)算,如在MIPS機(jī)器上add是進(jìn)行signed數(shù)相加,addu是進(jìn)行unsigned數(shù)相加,對(duì)指令進(jìn)行了區(qū)分,明確了使用場(chǎng)合。

x86的乘法和除法指令也進(jìn)行了區(qū)分,mul是無(wú)符號(hào)數(shù)乘法,imul是符號(hào)數(shù)乘法,div是無(wú)符號(hào)除法,idiv是符號(hào)數(shù)除法。另外,所有的條件轉(zhuǎn)移、條件傳送、條件設(shè)置指令會(huì)對(duì)指令運(yùn)算的結(jié)果進(jìn)行signed與unsigned的區(qū)分。

整數(shù)運(yùn)算規(guī)則

當(dāng)假定它是signed數(shù)時(shí),這個(gè)數(shù)需要使用另一種形式去解析,這就產(chǎn)生了signed數(shù)的表示方法。signed的表示法是以MSB(Most Significant Bit)作為符號(hào)位,MSB為1時(shí)是負(fù)數(shù),MSB為0時(shí)為正數(shù)。

以32位的數(shù)為例:0是正數(shù)的最小值,0x7FFFFFFF是正數(shù)的最大值,0x80000000是負(fù)數(shù)的最小值,0xFFFFFFFF是負(fù)數(shù)的最大值,超過(guò)這個(gè)表達(dá)范圍就產(chǎn)生了溢出情況。

0x7FFFFFFF+0x00000001結(jié)果為0x80000000,這個(gè)結(jié)果超過(guò)了32位正數(shù)能表達(dá)的最大值,于是就產(chǎn)生了溢出。兩個(gè)正數(shù)相加,32位的結(jié)果為負(fù)數(shù)(負(fù)數(shù)的最小值)。這個(gè)結(jié)果是錯(cuò)誤的。

signed數(shù)是以二進(jìn)制的補(bǔ)碼來(lái)表示,以4位二進(jìn)制數(shù)為例,求出-7的二進(jìn)制補(bǔ)碼形式。

-7的補(bǔ)碼形式是1001B,它的計(jì)算過(guò)程是:~7+1=-7。那么反過(guò)來(lái),1001B這個(gè)值是多少呢?從-7的求值過(guò)程可以推出:~((-7)-1),從而得出

由于1001B表達(dá)的是負(fù)數(shù),求值后要加上負(fù)號(hào),這就是我們所知道的十進(jìn)制的signed數(shù)。

1.2.2 floating-point數(shù)

現(xiàn)在的計(jì)算機(jī)浮點(diǎn)數(shù)格式都遵循IEEE754標(biāo)準(zhǔn)。在x86/x64體系中有三種浮點(diǎn)數(shù)。

① single-precision floating point(單精度浮點(diǎn)數(shù)):使用23位的精度。

② double-precision floating point(雙精度浮點(diǎn)數(shù)):使用52位的精度。

③ double extended-precision floating point(擴(kuò)展雙精度浮點(diǎn)數(shù)):使用64位的精度。

x87 FPU的硬件上使用擴(kuò)展雙精度浮點(diǎn)類型,所有浮點(diǎn)數(shù)最終都要轉(zhuǎn)為擴(kuò)展雙精度浮點(diǎn)數(shù)進(jìn)行處理(使用64位精度)。

二進(jìn)制格式

在計(jì)算機(jī)上,浮點(diǎn)數(shù)需要換化為二進(jìn)制格式進(jìn)行處理,分為3個(gè)部分:sign(符號(hào)位),exponent(指數(shù)位),以及significand(有效數(shù)位),如下所示。

最高位為符號(hào)位,單精度浮點(diǎn)數(shù)的exponent位是8位,significand位是23位;雙精度浮點(diǎn)數(shù)的exponent位是11位,significand位是52位。

在單精度和雙精度浮點(diǎn)數(shù)里,它們的significand部分有一個(gè)隱式的integer位(或被稱為J-bit),這個(gè)位的值固定為1。因此,單精度浮點(diǎn)數(shù)的精度實(shí)際為24位,而雙精度浮點(diǎn)數(shù)的精度實(shí)際為53位。

擴(kuò)展雙精度浮點(diǎn)數(shù)

在擴(kuò)展雙精度浮點(diǎn)數(shù)里,significand部分為64位,exponent為15位,如下所示。

在擴(kuò)展雙精度浮點(diǎn)數(shù)里,它的integer位是顯式的,在normal(合規(guī)的)數(shù)里,這個(gè)位必須為1,否則屬于denormal(不合規(guī)的)數(shù)。

normalized(規(guī)格化)

在IEEE754里,規(guī)格化是浮點(diǎn)數(shù)的基礎(chǔ)。正常情況下機(jī)器中的浮點(diǎn)數(shù)使用規(guī)格化的格式,這類浮點(diǎn)數(shù)被稱為normal數(shù)。

看看這個(gè)浮點(diǎn)數(shù):0.625,在機(jī)器中是如何表示的呢?

首先需要轉(zhuǎn)化為規(guī)格化的科學(xué)計(jì)數(shù)形式。

0.625=625/1000=5/8=5/23=101×2-3=1.01×2-1

于是0.625的二進(jìn)制科學(xué)計(jì)數(shù)法表達(dá)是1.01×2-1

在IEEE 754中規(guī)定規(guī)格化數(shù)的significand(有效數(shù))部分第1位是1,不能是0。如下所示。

接下來(lái),這個(gè)浮點(diǎn)數(shù)被轉(zhuǎn)化為單精度的二進(jìn)制形式,其值為0x3F200000。如下所示。

由于單精度浮點(diǎn)數(shù)的significand部分含有隱式的1值,因此在二進(jìn)制數(shù)格式里,significand部分的值為01000000...(即1.01中前面的1去掉)。

指數(shù)部分需要加上一個(gè)127值,這個(gè)值被稱為biased notation(移碼或校正值)。

biased notation(校正值)

biased notation用來(lái)解決浮點(diǎn)數(shù)使用integer方法進(jìn)行比較時(shí)出現(xiàn)的問(wèn)題。我們看看下面這兩個(gè)浮點(diǎn)數(shù)大小的比較:1.00×2-1和1.00×21

前面的指數(shù)為-1,后面的指數(shù)為1,指數(shù)大的那個(gè)必定會(huì)大。因此1.0×21的值是大于1.0×2-1的。在指數(shù)相同的情況下才需要對(duì)有效數(shù)部分進(jìn)行比較。

基于這種考慮,IEEE 754在浮點(diǎn)數(shù)的格式中,將指數(shù)部分安排在有效數(shù)前面,這樣就可以使用快速的整數(shù)比較方法來(lái)比較浮點(diǎn)數(shù)。

可是當(dāng)指數(shù)是負(fù)數(shù)時(shí),按照這樣的比較方法,會(huì)得出比指數(shù)為正數(shù)還要大的結(jié)論,這是錯(cuò)誤的。

1.0×2-1會(huì)比1.0×21要大!(因?yàn)椋?1的二進(jìn)制8位值為11111111)

為了解決這個(gè)問(wèn)題,于是引入了biased notation值,如下所示。

單精度的biased碼是127,雙精度的biased碼是1023,擴(kuò)展雙精度的biased碼是16383。這個(gè)biased碼值加上指數(shù)值,就得出了一個(gè)浮點(diǎn)數(shù)格式中的指數(shù)值:-1+127=126,1+127=128。

算一下。

1.0×2-1的二進(jìn)制序列是:001111110000000000000000000000000(0x3F000000)。

1.0×21的二進(jìn)制序列是:010000000000000000000000000000000(0x40000000)。

這樣就解決了在使用整數(shù)進(jìn)行比較時(shí),負(fù)的指數(shù)會(huì)比正的指數(shù)要大的問(wèn)題。

在nasm匯編語(yǔ)言語(yǔ)法里,可以使用一系列的宏來(lái)獲得浮點(diǎn)數(shù)以整數(shù)形式表現(xiàn)的常量值。

代碼清單1-4:

mov eax,__float32__(0.625)                    ; 獲得浮點(diǎn)數(shù)的整數(shù)形式值

__float32__(0.625)這個(gè)宏的求值結(jié)果是0x3f200000,即0.625的浮點(diǎn)數(shù)值是0x3f200000。

1.2.3 real number(實(shí)數(shù))與NaN(not a number)

IEEE754標(biāo)準(zhǔn)定義了多種實(shí)數(shù)的編碼格式,它們包括以下面幾種格式。

① zero:包括+0.0和-0.0編碼。

② denormal數(shù)字:不合規(guī)格的數(shù),denormal數(shù)有時(shí)候被稱為tiny(極小)數(shù)。

③ normal數(shù)字:合規(guī)的普通浮點(diǎn)數(shù),這是一個(gè)finite(有限的)取值范圍。

④ infinite(無(wú)限)數(shù)字:包括+∞(正無(wú)窮大數(shù))和-∞(負(fù)無(wú)窮大數(shù))。

⑤ NaN(not a number):包括SNaN和QNaN。

其編碼值如下所示。

zero

在0的編碼里,exponent和significand都為0。+0(正零)和-0(負(fù)零)的值是相等的。

denormal(不合規(guī))數(shù)

denormal數(shù)是一個(gè)極小的數(shù)(即tiny數(shù)),接近于0值。它是一種不合規(guī)的表示方法。denormal數(shù)的exponent部分為0值。不同于zero值,它的significand部分不為0值(在擴(kuò)展雙精度下exponent為0值,J位為0值也屬于denormal數(shù))。因此,下面的數(shù)是denormal數(shù)。

① 00000001H:exponent為0,significand不為0。

② 007FFFFFH:exponent為0,significand不為0。

normal(合規(guī))數(shù)

normal數(shù)是在finite(有限)集合里的一個(gè)數(shù)。在normal數(shù)編碼中,J位的值必須為1(在擴(kuò)展雙精度下)。在單精度和雙精度里,J位(或稱integer位)是隱式的,固定為1值。

上面列出了三種浮點(diǎn)格式的exponent和significand取值范圍,于是有以下結(jié)論。

① 單精度的表達(dá)范圍,正數(shù)是0x00800000~0x7F7FFFFF,負(fù)數(shù)是0x80800000~FF7FFFFF。

② 雙精度的表達(dá)范圍,正數(shù)是0x00100000_00000000~0x7FEFFFFF_FFFFFFFF,負(fù)數(shù)是0x80100000_00000000~0xFFEFFFFF_FFFFFFFF。

③ 擴(kuò)展雙精度的表達(dá)范圍,正數(shù)是0x0001_800000000_00000000~0x7FFE_FFFFFFFF_FFFFFFFF,負(fù)數(shù)是0x8001_80000000_00000000~0xFFFE_FFFFFFFF_FFFFFFFF。

用科學(xué)計(jì)數(shù)法表示如下。

① 單精度:2-126到2127×1.11...(23個(gè)1),因此X的取值就是2-126<=X<2128

② 雙精度:2-1022到21023×1.11...(52個(gè)1),因此X的取值就是2-1022<=X<21024

③ 擴(kuò)展雙精度:2-16382到216383×1.11...(64個(gè)1),因此X的取值就是2-16382<=X<216384

infinite(無(wú)窮大)數(shù)

顯然,這是與finite數(shù)相對(duì)的。在無(wú)窮大數(shù)里值是固定的,分為+∞(正無(wú)窮大)和-∞(負(fù)無(wú)窮大)。exponent和significand的值如下所示。

對(duì)于擴(kuò)展雙精度來(lái)說(shuō),由于它的J位是顯式的,必須為1值(否則是unsupported類型),因此significand的值為0x80000000_00000000。

NaN(not a number)數(shù)

如果一個(gè)數(shù)超出infinite,那就是一個(gè)NaN(not a number)數(shù)。在NaN數(shù)中,它的exponent部分為可表達(dá)的最大值,即FF(單精度)、7FF(雙精度)和7FFF(擴(kuò)展雙精度)。

NaN數(shù)與infinite數(shù)的區(qū)別是:infinite數(shù)的significand部分為0值(擴(kuò)展雙精度的bit63位為1)。而NaN數(shù)的significand部分不為0值。

NaN數(shù)包括下列兩類。

① SNaN(Signaling NaN)數(shù):SNaN數(shù)表示是一種比較嚴(yán)重的錯(cuò)誤值。

② QNaN(Quiet NaN)數(shù):在一般情況下,QNaN數(shù)是可接受的。

SNaN和QNaN數(shù)的編碼區(qū)別在于significand部分的不同,如下所示。

SNaN數(shù)的significand以1.0開(kāi)頭(并且1.0后面的位不為0值),而QNaN數(shù)的significand是1.1開(kāi)頭。

x87 FPU或SSE指令遇到SNaN數(shù)時(shí)會(huì)產(chǎn)生#IA異常,而遇到QNaN時(shí)不產(chǎn)生#IA異常(部分指令除外)。

1.2.4 unsupported編碼值

如果一個(gè)數(shù)的編碼值不在1.2.3節(jié)所描述的格式里,那么它就屬于unsupported類型的編碼值。unsupported類型的編碼有三類,它們的J位都為0值,如下所示。

下面的擴(kuò)展雙精度編碼都屬于unsupported類型。

① 0x7FFE_00000000_00000000。

② 0x7FFF_00000000_00000000。

③ 0x7FFF_00000000_00000001。

由于單精度和雙精度中J位隱式固定為1值,因此實(shí)際上也僅有擴(kuò)展雙精度會(huì)出現(xiàn)unsupported編碼。

實(shí)驗(yàn)1-2:打印各種編碼及信息

下面,我們做個(gè)測(cè)試,輸出x87 FPU的stack中的編碼值及狀態(tài)信息,代碼如下。

代碼清單1-5(topic01\ex1-2\protected.asm):

finit       ; 初始化 x87 FPU
fld TWORD [QNaN]    ; 加載 QNaN 數(shù)
fld TWORD [SNaN]    ; 加載 SNaN 數(shù)
fld TWORD [denormal]   ; 加載 denormal 數(shù)
fld TWORD [infinity]   ; 加載 infinity 數(shù)
fld TWORD [unsupported]  ; 加載 unsupported 數(shù)
fldz       ; 加載 0 值
fld1       ; 加載 1.0 值
call dump_data_register  ; 打印信息

關(guān)于這個(gè)例子的代碼,請(qǐng)參考第20章的相關(guān)內(nèi)容。

上面是運(yùn)行在Bochs里的結(jié)果圖:這里顯示了x87 FPU的stack寄存器的8種狀態(tài),除empty狀態(tài)的編碼值是一個(gè)indefinite(不確定)值外,其余數(shù)的編碼都是正確的。

1.2.5 浮點(diǎn)數(shù)精度的轉(zhuǎn)換

在x86/x64體系里,由于x87 FPU硬件使用擴(kuò)展雙精度格式,因此必然會(huì)遇到single/double precision格式與double extended-precision格式之間的互換問(wèn)題。

轉(zhuǎn)換為擴(kuò)展雙精度數(shù)

當(dāng)由單精度數(shù)或雙精度轉(zhuǎn)換為擴(kuò)展雙精度數(shù)時(shí),exponent部分必須基于擴(kuò)展雙精度數(shù)的biased碼來(lái)調(diào)整。于是擴(kuò)展雙精度數(shù)的exponent值為:

① 從單精度轉(zhuǎn)化:exponent–127+16383。

② 從雙精度轉(zhuǎn)化:exponent–1023+16383。

而擴(kuò)展雙精度數(shù)的significand部分,由單/雙精度數(shù)的significand部分移植過(guò)來(lái)。

以單精度數(shù)1.11...×2120為例,它轉(zhuǎn)換為擴(kuò)展雙精度的過(guò)程如下所示。

單精度數(shù)1.11...×2120的編碼值為0x7BFFFFFF,它的exponent值為0xF7(11110111B),significand部分全為1值。

于是擴(kuò)展雙精度數(shù)的exponent值為0xF7-127+16383=0x4077(1000000001110111B),單精度23位的significand部分直接移到擴(kuò)展雙精度的bit62到bit40位,低40位補(bǔ)0。

最終的擴(kuò)展雙精度編碼值為0x4077_FFFFFF00_00000000。而對(duì)于雙精度數(shù)來(lái)說(shuō):52位的significand部分將直接移到擴(kuò)展雙精度的bit62到bit11位。

擴(kuò)展雙精度數(shù)轉(zhuǎn)換為單精度數(shù)

而從擴(kuò)展雙精度轉(zhuǎn)換為單/雙精度數(shù)的情形會(huì)復(fù)雜得多,涉及目標(biāo)格式的precison(精度)問(wèn)題。當(dāng)擴(kuò)展雙精度significand部分的值超出目標(biāo)格式的精度時(shí),就會(huì)發(fā)生rounded(舍入)操作,從而引發(fā)precision異常。

要檢查超出精度的significand是否為0值,如下所示。

這部分不為0值時(shí),就會(huì)發(fā)生rounded操作。

下面,我們以擴(kuò)展雙精度數(shù)1.11...×2120轉(zhuǎn)化為單精度格式為例進(jìn)行描述。當(dāng)1.11...×2120為擴(kuò)展雙精度格式時(shí),它的編碼值為0x4077_FFFFFFFF_FFFFFFFF。

目標(biāo)格式exponent部分的計(jì)算如下。

① 單精度數(shù):exponent-16383+127。

② 雙精度數(shù):exponent-16383+1023。

這個(gè)轉(zhuǎn)換過(guò)程較為復(fù)雜,如下所示。

圖中的陰影部分是超出精度的significand部分(bit 39~bit 0),它的值不為0,需要進(jìn)行rounded操作,在x87 FPU中這個(gè)舍入依賴于rounded控制位。

IEEE754定義了以下4種舍入模式。

① round to nearest模式:朝±∞(正和負(fù)方向的無(wú)窮大值)方向舍入。

② round down模式:正數(shù)朝最大normal值舍入,負(fù)數(shù)朝-∞方向舍入。

③ round up模式:正數(shù)朝+∞方向舍入,負(fù)數(shù)朝最大normal值舍入。

④ round zero模式:正數(shù)和負(fù)數(shù)都朝最大normal值舍入。

上圖中的舍入是朝+∞方向舍入,如圖所示:bit 39的值為1,它將向bit 40進(jìn)行舍入,效果等于+1值。目標(biāo)格式中的significand部分舍入的結(jié)果值為0。

目標(biāo)格式的exponent部分為擴(kuò)展雙精度的exponent-16383+127=0xF7(11110111B),可是由于significand部分還是進(jìn)位值,因此目標(biāo)格式的最終exponent部分為0xF8(加上1值)。

因此,最終轉(zhuǎn)換的單精度值為0x7C000000,轉(zhuǎn)換得到的浮點(diǎn)數(shù)是1.0...×2121,結(jié)果大于原來(lái)的擴(kuò)展雙精度浮點(diǎn)數(shù)。

擴(kuò)展雙精度數(shù)轉(zhuǎn)換為雙精度數(shù)

這和轉(zhuǎn)換為單精度數(shù)是一致的。在雙精度格式里,它的精度是52位,因此超出精度部分為bit10到bit0位。

exponent的計(jì)算是擴(kuò)展雙精度的exponent-16383+1023。

1.2.6 浮點(diǎn)數(shù)的溢出

1.2.5節(jié)所探討的精度轉(zhuǎn)換處于目標(biāo)格式的finite(有限的)表達(dá)范圍內(nèi),這是一種正常的轉(zhuǎn)換行為。然而在某些時(shí)候會(huì)遇到目標(biāo)格式不能表達(dá)的結(jié)果值,產(chǎn)生溢出。

在浮點(diǎn)數(shù)里,溢出分為兩種。

① overflow(向上溢出):結(jié)果值超出了目標(biāo)格式的最大normal值(即finite范圍外)。

② underflow(向下溢出):結(jié)果值超出了目標(biāo)格式的最小normal值(即tiny值或denormal數(shù))。

上圖是一個(gè)實(shí)數(shù)軸上的分布圖,denormal(tiny)接近于0值,infinite(無(wú)窮大)數(shù)接近于NaN數(shù),因此overflow和underflow溢出的條件如下。

在這里符號(hào)位不重要,正或負(fù)都能產(chǎn)生溢出。

1.2.6.1 underflow(向下溢出)

在x86/x64體系里,x87 FPU指令的rounded(舍入)結(jié)果值發(fā)生underflow時(shí)會(huì)產(chǎn)生#U(underflow)異常,并在x87 FPU的status寄存器里記錄下來(lái)。

從前面所述我們知道,結(jié)果值達(dá)到tiny值時(shí)就表示發(fā)生了underflow溢出,各種精度tiny值如下所示。

這個(gè)將要達(dá)到tiny的臨界值,就是目標(biāo)精度的normal的最小值,當(dāng)小于這個(gè)最小值時(shí),就發(fā)生underflow溢出,對(duì)于underflow的處理,如下所示。

下面我們還是看看擴(kuò)展雙精度數(shù)轉(zhuǎn)換為單精度格式。

例子:將擴(kuò)展雙精度值0x3F80_C0000300_00000000轉(zhuǎn)換為單精度值。

這個(gè)值表示1.100...0110...000×2-127,它的bit63、bit62、bit41和bit 40位都為1值,如圖所示。

這個(gè)轉(zhuǎn)換過(guò)程中會(huì)對(duì)underflow進(jìn)行如下處理。

判斷exponent是否溢出

單精度normal數(shù)的指數(shù)部應(yīng)有-126<=X<128,也就是說(shuō),在二進(jìn)制編碼值中exponent的值應(yīng)為1<=exponent<255。關(guān)于normal的finite(有限)范圍請(qǐng)參考1.2.3節(jié)的normal數(shù)的描述。

由于這個(gè)值的指數(shù)部分為-127,因此對(duì)于單精度格式來(lái)說(shuō),它已經(jīng)是underflow溢出了。

significand右移

在發(fā)生underflow的情況下,significand部分需要向右移位。下面就是一個(gè)significand右移的例子。

右移位數(shù)的計(jì)算方法是:指數(shù)的絕對(duì)值減去-126的絕對(duì)值(即減去126)。在本例里是向右移動(dòng)一位,移位后的significand部分變成0.110...0110...000。

如下所示:目標(biāo)單精度格式的exponent值將為0值,significand部分(包括J位)右移1位后,J位將變?yōu)?值(這是一個(gè)denormal數(shù))。

精度舍入

如果significand的超出精度部分不為0值,同樣會(huì)發(fā)生rounded(舍入)操作。

如上所示:bit 39位向bit 40進(jìn)行舍入,效果等于加上1值。0值將會(huì)寫(xiě)入目標(biāo)單精度值的exponent部分。最終得到的目標(biāo)單精度值為0x00600002。

在x87 FPU里這個(gè)操作會(huì)引發(fā)#U(underflow)異常和#P(precision)異常,關(guān)于#U和#P異常的更多信息,請(qǐng)參考20.2.6節(jié)的相關(guān)描述。

1.2.6.2 overflow(向上溢出)

當(dāng)結(jié)果值達(dá)到infinite(無(wú)限或無(wú)窮大)數(shù)時(shí),就會(huì)產(chǎn)生overflow溢出。在x87 FPU中,當(dāng)伴隨著目標(biāo)格式的precision(精度)不能表達(dá)目標(biāo)值時(shí),就會(huì)產(chǎn)生#P(precision)異常和#O(overflow)異常。

如上所示:infinite值是一個(gè)overflow的臨界值,等于或大于這個(gè)值時(shí)就發(fā)生overflow。因此,當(dāng)將擴(kuò)展雙精度數(shù)1.0×2128轉(zhuǎn)換為單精度,或者將擴(kuò)展雙精度數(shù)1.0×21024轉(zhuǎn)換為雙精度數(shù)時(shí),就會(huì)產(chǎn)生overflow。

1.2.7 BCD碼

在BCD碼中,一個(gè)十進(jìn)制數(shù)的每一位,使用8位的二進(jìn)制進(jìn)行編碼。以十進(jìn)制數(shù)15為例,它的BCD編碼值為15H,如下所示。

每個(gè)BCD碼用1個(gè)字節(jié)來(lái)表示,這是非壓縮的BCD碼形式。BCD可以使用在GPI(通用指令)里,如下所示。

mov ax,9     ; AX 為 9(BCD 9)
mov bx,8     ; BX 為 8(BCD 8)
add ax,bx     ; AX 值為11H
aaa       ; 執(zhí)行 BCD 調(diào)整后,AX 值為 0107H(BCD 17)

在使用ADD指令相加后(假如兩個(gè)BCD碼相加),AAA指令將AX寄存器調(diào)整為非壓縮的BCD碼,AX結(jié)果值為0107H(即表示非壓縮的BCD碼17)。

packed BCD(壓縮的BCD碼)

非壓縮的BCD碼浪費(fèi)了一半的空間,在packed BCD碼里,每個(gè)BCD數(shù)字使用4位來(lái)表示,如上圖所示。

packed BCD碼能使用在x87 FPU指令里。FBLD指令能加載一個(gè)80位寬的packed BCD碼進(jìn)入x87 FPU數(shù)據(jù)寄存器里,這個(gè)80位的值可容納18個(gè)packed BCD碼。

1.2.8 SIMD數(shù)據(jù)

在SSE系列指令(SSE到SSE4.2)以及AVX指令里處理的數(shù)據(jù)分為以下兩大類。

① vector(packed)與scalar類型的浮點(diǎn)數(shù)據(jù)。

② packed integer數(shù)據(jù)。

這些數(shù)據(jù)類型使得SIMD指令能一次性處理多個(gè)數(shù)據(jù),加大吞吐量。

128位與256位的vector floating-point數(shù)據(jù)

從SSE到SSE4.2指令集里,使用128位的XMM寄存器,支持128位的vector(矢量)浮點(diǎn)數(shù)據(jù)。而在VAX和FMA指令里增加到了256位的YMM寄存器,可以使用256位的vector數(shù)據(jù)。

如下所示:在128位vector數(shù)據(jù)里,可以容納4個(gè)單精度浮點(diǎn)數(shù)或者2個(gè)雙精度浮點(diǎn)數(shù)。在VAX和FMA指令上的YMM寄存器里可以容納8個(gè)單精度浮點(diǎn)數(shù)或者4個(gè)雙精度浮點(diǎn)數(shù)。

128位的scalar floating-point數(shù)據(jù)

在SSE系列指令和AVX/FMA指令里處理的scalar數(shù)據(jù)是128位寬,如下所示。

在128位的scalar數(shù)據(jù)里,單精度scalar只使用低32位,雙精度scalar只使用低64位,高位不作為數(shù)據(jù)進(jìn)行運(yùn)算。

128位與256位的packed integer數(shù)據(jù)

在SSE系列指令和AVX指令里處理的packed integer數(shù)據(jù)也是128位寬,包括以下幾種。

① packed byte。

② packed word。

③ packed doubleword。

④ packed quadword。

多數(shù)情況下,這些整型數(shù)也區(qū)分unsigned和signed版本,由對(duì)應(yīng)的SIMD指令使用。然而Intel在Ivy Bridge微架構(gòu)里增加了AVX2指令,AVX2指令能夠處理256位的packed integer數(shù)據(jù)。

如下所示,128位的packed integer數(shù)據(jù)能容納16個(gè)byte,8個(gè)word,4個(gè)doubleword或者2個(gè)quadword整型值。

當(dāng)使用AVX2指令(AVX的擴(kuò)展指令集)時(shí),還可以使用256位的packed integer數(shù)據(jù),能容納32個(gè)byte,16個(gè)word,8個(gè)doubleword或者4個(gè)quadword整型值。

主站蜘蛛池模板: 禹州市| 苍山县| 霍邱县| 来凤县| 九江市| 景谷| 礼泉县| 安仁县| 图们市| 博客| 潜山县| 临颍县| 阳春市| 出国| 松滋市| 荥经县| 阿瓦提县| 阳朔县| 蒲城县| 丰都县| 九龙县| 来凤县| 定西市| 保康县| 兴和县| 古浪县| 响水县| 靖边县| 枣庄市| 察隅县| 万山特区| 东阿县| 军事| 普定县| 永吉县| 正镶白旗| 望江县| 涿鹿县| 高雄县| 蒲江县| 屏东市|