- x86/x64體系探索及編程
- 鄧志著
- 131字
- 2019-03-01 11:49:27
第一篇 x86基礎
這一篇探討x86架構的基礎平臺知識,有如下7章。
第1章 數與數據類型
第2章 x86/x64編程基礎
第3章 編寫本書的實驗例子
第4章 處理器的身份
第5章 了解Flags
第6章 處理器的控制寄存器
第7章 MSR
第3章很重要,講解如何編寫、生成和運行本書的所有實驗例子。在繼續本書后續章節前應好好了解這一章里的內容。
第1章 數與數據類型
我們知道在計算機中處理的數是按照一定的規則進行組織和存放的。其中的每個數按特定的編碼規則組織。可是光有這些數的組織規則還是不夠,計算機每條指令的操作數可能會有不同的數據類型。那么計算機能處理哪些數據類型呢?在這一章里,我們將要了解數與數據類型。
1.1 數
計算機能處理各種各樣的信息,計算機硬件對數據進行處理后,可呈現出各種各樣的信息。
1.1.1 數字
數字是個基本的計數符號。通用的數字有10個:0,1,2,3,4,5,6,7,8,9。以這些數字組合構成的數是十進制數。
思考各個進制數的數字。
1.二進制數字
包括0和1。
2.八進制數字
包括0,1,2,3,4,5,6,7。
3.十進制數字
包括0,1,2,3,4,5,6,7,8,9。
4.十六進制數字
包括0,1,2,3,4,5,6,7,8,9及字母A,B,C,D,E,F。
各個進制以相應的數字表達的計數范圍作為base值,如:二進制的base值是2,八進制的base值是8,十進制的base值是10,十六進制的base值是16。
1.1.2 二進制數
二進制數是計算機運算的基礎,無論何種制式的數,在計算機中都是以二進制形式存放的。由二進制數字組成的數字序列是二進制數,如下所示。
二進制數組合里,每個數位被稱為bit(位),能表達值0和1。二進制數的base值是2,那么在n個二進制數字的序列中,其值為
值=(Dn-1×2n-1)+(Dn-2×2n-2)+…+(D1×21)+(D0×20)
這是一個數學上的算式。這個值是我們很容易辨識的十進制值。
1.1.3 二進制數的排列
在日常的書寫或表達上,最左邊的位是最高位。數的位排列從左到右,對應的值從高到低。可是在機器的數字電路上,數的高低位可以從左到右進行排列,也可以從右到左進行排列。這樣就產生了MSB和LSB的概念。
什么是MSB?什么是LSB?
以一個自然的二進制表達序列上32位的二進制數為例,最右邊是bit 0,最左邊是bit 31。那么bit 0就用LSB(Least Significant Bit,最低有效位)來表示,bit 31就用MSB(Most Significant Bit,最高有效位)來表示。
MSB也用做符號位(1為負,0為正),但若在無符號數上,則MSB就是數的最高位,LSB是數的最低位。無論一個數在機器上是從左到右排列,還是從右到左排列,使用MSB和LSB的概念都很容易對其二進制形式進行描述說明。
小端序與大端序
二進制數在計算機的組織存放中,地址由低位到高位對應著兩種排列。
① 由LSB到MSB,這就是小端序(little-endian)排法。
② 由MSB到LSB,這就是大端序(big-endian)排法。
在x86/x64體系中使用的是小端序存儲格式,也就是:MSB對應著存儲器地址的高位,LSB對應著存儲器地址的低位。
在有些RISC(精簡指令集計算機)體系里,典型的如Power/PowerPC系列,使用大端序排法。即在由低到高的地址位里,依次存放MSB到LSB。亦即:MSB存放在存儲器地址的低位,LSB存放在高位。
代碼清單1-1:
mov dword [Foo],1 test byte [Foo],1 ; 測試 LSB 是否存放在低端上 jnz IS_little_endian ; 是小端序
上面的代碼將1存放在32位的內存里,通過讀取內存的低字節來判斷1到底存放在低字節還是高字節,從而區分是小端序還是大端序。
某些RISC機器上是可以在大端序與小端序存儲序列之間做選擇的。大端序格式看上去更符合人類表達習慣,而小端序看上去不那么直觀,不過這對于計算機的處理邏輯并無影響。
實驗1-1:測試字節內的位排列
字節內的位是否有大端序和小端序之分?這似乎沒有定論,我們不是硬件設計人員,很難做出判斷。筆者傾向于認為位的排列是區分的。
從代碼清單1-1我們可以測試機器是屬于小端序還是大端序,原理是根據字節在內存中的存儲序列進行判斷。對代碼稍做修改,即可用來測試位的排列,如代碼清單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中測試內存中的bit 1是否為存進去的值1,然后輸出一條信息。下面是這個實驗在真實計算機上的測試結果。
實際上這個方法未必能測出什么(如果CPU一次訪問字節,這個測試結果并不能說明什么)。按這個方法測出字節內也是小端序排列的。
完整的測試代碼在topic01\ex1-1\目錄里。關于如何在真機上進行測試,請看第3章。
1.1.4 十六進制數
在二進制數中,每一個數位只能表達兩個值。如果要表達一個很大的數,就將比較麻煩。而十六進制數能在更短的序列里表達更大的數值范圍。
十六進制數由十六進制數字組成,每個數位能表達16個值:從0到15。
超過9的計數用字母來代替,A~F分別表示10~15,它的base值就是16,同樣由以下的計算式子來求十六進制的值:
值=(Dn-1×16n-1)+(Dn-2×16n-2)+…+(D1×161)+(D0×160)
一個十六進制數字能表達4個二進制數字。與二進制數相比,采用十六進制數進行計數大大方便了書寫,也方便了閱讀。
1.1.5 八進制數與十進制數
同樣,八進制數由八進制數字組成,十進制數由十進制數字組成。我們的日常生活中常使用十進制數。而二進制數、八進制數及十六進制數表達的數,并不被我們直觀地識別。
為了書寫及識別方便,會使用前綴或者后綴對數進行修飾。
使用后綴:d(D)或默認描述為十進制數,b(B)為二進制數,h(H)為十六進制數,o(O)為八進制數。
上面是常用的修飾方式,在C語言中對十六進制數常使用前綴0x進行修飾,由于C語言的影響力現在使用0x前綴很通用。
在nasm語言里數的修飾更多了,下面是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 數據類型
在x86/x64體系中,指令處理的數據分為fundamental(基礎)和numeric(數值)兩大類。基礎類型包括:byte(8位),word(16位),doubleword(32位),以及quadword(64位),它們代表指令能一次性處理的數據寬度。
numeric數據類型使用在運算類指令上,總結來說x86/x64體系的運算類指令能處理下面四大類數據。
① integer(整型數):包括unsigned類型和singed類型。
② floating-point(浮點數):包括single-precision floating-point(單精度浮點數),double-precision floating-point(雙精度浮點數),以及double extended-precision floatingpoint(擴展雙精度浮點數)。
③ BCD(binary-code decmial integer):包括non-packed BCD碼和packed-BCD碼。
④ SIMD(single instruction,multiple data):這是屬于packed類型的數據。
SIMD數據是在一個operand(操作數)里集成了多個integer、floating-point或者BCD數據。SIMD指令可以一性次同時處理這些數據。
1.2.1 integer數
在計算機處理中,整數會區分signed(有符號數)和unsigned(無符號數)兩種情況,數值的MSB值被作為符號位。每個數值類型有自己的取值范圍,如下所示。
可是在計算機中根本無法判斷一個整數是signed數還是unsigned數。例如0ABh這個整數就無法知道它是signed數還是unsigned數。
計算機能做到的是:在整數的使用中,在應該使用signed數的場合下認為它是signed數,而在使用unsigned數的場合下認為它是unsigned數。
在這種假定下,即使不是signed數也會被當做signed數進行處理。既然這樣,在計算機運算中就無須判斷是signed數還是unsigned數,只需假定它是signed數或是unsigned數。
而在浮點數上,每個浮點數都有符號位,因此浮點數能夠清楚地識別它就是signed數。所以浮點數不存在unsigned數的情況。
在x86機器上,對整數的加減法運算過程中不會識別signed數與unsigned數,而根據signed與unsigned兩種運算結果進行相應的eflags標志位設置。
代碼清單1-3:
mov eax,0x70000000 mov ebx,0x80000000 sub eax,ebx
上面的代碼中,0x70000000和0x80000000是signed數還是unsigned數呢?
二進制運算結果值是0xF0000000,指令會同時對結果進行兩種分析設置。
為signed時
假定運算雙方是signed數時,這個結果是錯誤的,它產生了溢出。它會置eflags寄存器的OF(Overflow Flag)標志為1,以及SF(Sign Flag)標志為1,表示結果為負數。
為unsigned時
假定雙方是unsigned數時,它會置CF(Carry Flag)標志為1,表示產生了借位。
因此:這條指令會同時對OF、SF及CF標志置位。而對這個結果如何運用那是程序員的職責。
另外,RISC體系的機器普遍會在指令層上做假定運算,如在MIPS機器上add是進行signed數相加,addu是進行unsigned數相加,對指令進行了區分,明確了使用場合。
x86的乘法和除法指令也進行了區分,mul是無符號數乘法,imul是符號數乘法,div是無符號除法,idiv是符號數除法。另外,所有的條件轉移、條件傳送、條件設置指令會對指令運算的結果進行signed與unsigned的區分。
整數運算規則
當假定它是signed數時,這個數需要使用另一種形式去解析,這就產生了signed數的表示方法。signed的表示法是以MSB(Most Significant Bit)作為符號位,MSB為1時是負數,MSB為0時為正數。
以32位的數為例:0是正數的最小值,0x7FFFFFFF是正數的最大值,0x80000000是負數的最小值,0xFFFFFFFF是負數的最大值,超過這個表達范圍就產生了溢出情況。
0x7FFFFFFF+0x00000001結果為0x80000000,這個結果超過了32位正數能表達的最大值,于是就產生了溢出。兩個正數相加,32位的結果為負數(負數的最小值)。這個結果是錯誤的。
signed數是以二進制的補碼來表示,以4位二進制數為例,求出-7的二進制補碼形式。
-7的補碼形式是1001B,它的計算過程是:~7+1=-7。那么反過來,1001B這個值是多少呢?從-7的求值過程可以推出:~((-7)-1),從而得出
由于1001B表達的是負數,求值后要加上負號,這就是我們所知道的十進制的signed數。
1.2.2 floating-point數
現在的計算機浮點數格式都遵循IEEE754標準。在x86/x64體系中有三種浮點數。
① single-precision floating point(單精度浮點數):使用23位的精度。
② double-precision floating point(雙精度浮點數):使用52位的精度。
③ double extended-precision floating point(擴展雙精度浮點數):使用64位的精度。
x87 FPU的硬件上使用擴展雙精度浮點類型,所有浮點數最終都要轉為擴展雙精度浮點數進行處理(使用64位精度)。
二進制格式
在計算機上,浮點數需要換化為二進制格式進行處理,分為3個部分:sign(符號位),exponent(指數位),以及significand(有效數位),如下所示。
最高位為符號位,單精度浮點數的exponent位是8位,significand位是23位;雙精度浮點數的exponent位是11位,significand位是52位。
在單精度和雙精度浮點數里,它們的significand部分有一個隱式的integer位(或被稱為J-bit),這個位的值固定為1。因此,單精度浮點數的精度實際為24位,而雙精度浮點數的精度實際為53位。
擴展雙精度浮點數
在擴展雙精度浮點數里,significand部分為64位,exponent為15位,如下所示。
在擴展雙精度浮點數里,它的integer位是顯式的,在normal(合規的)數里,這個位必須為1,否則屬于denormal(不合規的)數。
normalized(規格化)
在IEEE754里,規格化是浮點數的基礎。正常情況下機器中的浮點數使用規格化的格式,這類浮點數被稱為normal數。
看看這個浮點數:0.625,在機器中是如何表示的呢?
首先需要轉化為規格化的科學計數形式。
0.625=625/1000=5/8=5/23=101×2-3=1.01×2-1
于是0.625的二進制科學計數法表達是1.01×2-1。
在IEEE 754中規定規格化數的significand(有效數)部分第1位是1,不能是0。如下所示。
接下來,這個浮點數被轉化為單精度的二進制形式,其值為0x3F200000。如下所示。
由于單精度浮點數的significand部分含有隱式的1值,因此在二進制數格式里,significand部分的值為01000000...(即1.01中前面的1去掉)。
指數部分需要加上一個127值,這個值被稱為biased notation(移碼或校正值)。
biased notation(校正值)
biased notation用來解決浮點數使用integer方法進行比較時出現的問題。我們看看下面這兩個浮點數大小的比較:1.00×2-1和1.00×21。
前面的指數為-1,后面的指數為1,指數大的那個必定會大。因此1.0×21的值是大于1.0×2-1的。在指數相同的情況下才需要對有效數部分進行比較。
基于這種考慮,IEEE 754在浮點數的格式中,將指數部分安排在有效數前面,這樣就可以使用快速的整數比較方法來比較浮點數。
可是當指數是負數時,按照這樣的比較方法,會得出比指數為正數還要大的結論,這是錯誤的。
1.0×2-1會比1.0×21要大!(因為:-1的二進制8位值為11111111)
為了解決這個問題,于是引入了biased notation值,如下所示。
單精度的biased碼是127,雙精度的biased碼是1023,擴展雙精度的biased碼是16383。這個biased碼值加上指數值,就得出了一個浮點數格式中的指數值:-1+127=126,1+127=128。
算一下。
1.0×2-1的二進制序列是:001111110000000000000000000000000(0x3F000000)。
1.0×21的二進制序列是:010000000000000000000000000000000(0x40000000)。
這樣就解決了在使用整數進行比較時,負的指數會比正的指數要大的問題。
在nasm匯編語言語法里,可以使用一系列的宏來獲得浮點數以整數形式表現的常量值。
代碼清單1-4:
mov eax,__float32__(0.625) ; 獲得浮點數的整數形式值
__float32__(0.625)這個宏的求值結果是0x3f200000,即0.625的浮點數值是0x3f200000。
1.2.3 real number(實數)與NaN(not a number)
IEEE754標準定義了多種實數的編碼格式,它們包括以下面幾種格式。
① zero:包括+0.0和-0.0編碼。
② denormal數字:不合規格的數,denormal數有時候被稱為tiny(極小)數。
③ normal數字:合規的普通浮點數,這是一個finite(有限的)取值范圍。
④ infinite(無限)數字:包括+∞(正無窮大數)和-∞(負無窮大數)。
⑤ NaN(not a number):包括SNaN和QNaN。
其編碼值如下所示。
zero
在0的編碼里,exponent和significand都為0。+0(正零)和-0(負零)的值是相等的。
denormal(不合規)數
denormal數是一個極小的數(即tiny數),接近于0值。它是一種不合規的表示方法。denormal數的exponent部分為0值。不同于zero值,它的significand部分不為0值(在擴展雙精度下exponent為0值,J位為0值也屬于denormal數)。因此,下面的數是denormal數。
① 00000001H:exponent為0,significand不為0。
② 007FFFFFH:exponent為0,significand不為0。
normal(合規)數
normal數是在finite(有限)集合里的一個數。在normal數編碼中,J位的值必須為1(在擴展雙精度下)。在單精度和雙精度里,J位(或稱integer位)是隱式的,固定為1值。
上面列出了三種浮點格式的exponent和significand取值范圍,于是有以下結論。
① 單精度的表達范圍,正數是0x00800000~0x7F7FFFFF,負數是0x80800000~FF7FFFFF。
② 雙精度的表達范圍,正數是0x00100000_00000000~0x7FEFFFFF_FFFFFFFF,負數是0x80100000_00000000~0xFFEFFFFF_FFFFFFFF。
③ 擴展雙精度的表達范圍,正數是0x0001_800000000_00000000~0x7FFE_FFFFFFFF_FFFFFFFF,負數是0x8001_80000000_00000000~0xFFFE_FFFFFFFF_FFFFFFFF。
用科學計數法表示如下。
① 單精度:2-126到2127×1.11...(23個1),因此X的取值就是2-126<=X<2128。
② 雙精度:2-1022到21023×1.11...(52個1),因此X的取值就是2-1022<=X<21024。
③ 擴展雙精度:2-16382到216383×1.11...(64個1),因此X的取值就是2-16382<=X<216384。
infinite(無窮大)數
顯然,這是與finite數相對的。在無窮大數里值是固定的,分為+∞(正無窮大)和-∞(負無窮大)。exponent和significand的值如下所示。
對于擴展雙精度來說,由于它的J位是顯式的,必須為1值(否則是unsupported類型),因此significand的值為0x80000000_00000000。
NaN(not a number)數
如果一個數超出infinite,那就是一個NaN(not a number)數。在NaN數中,它的exponent部分為可表達的最大值,即FF(單精度)、7FF(雙精度)和7FFF(擴展雙精度)。
NaN數與infinite數的區別是:infinite數的significand部分為0值(擴展雙精度的bit63位為1)。而NaN數的significand部分不為0值。
NaN數包括下列兩類。
① SNaN(Signaling NaN)數:SNaN數表示是一種比較嚴重的錯誤值。
② QNaN(Quiet NaN)數:在一般情況下,QNaN數是可接受的。
SNaN和QNaN數的編碼區別在于significand部分的不同,如下所示。
SNaN數的significand以1.0開頭(并且1.0后面的位不為0值),而QNaN數的significand是1.1開頭。
x87 FPU或SSE指令遇到SNaN數時會產生#IA異常,而遇到QNaN時不產生#IA異常(部分指令除外)。
1.2.4 unsupported編碼值
如果一個數的編碼值不在1.2.3節所描述的格式里,那么它就屬于unsupported類型的編碼值。unsupported類型的編碼有三類,它們的J位都為0值,如下所示。
下面的擴展雙精度編碼都屬于unsupported類型。
① 0x7FFE_00000000_00000000。
② 0x7FFF_00000000_00000000。
③ 0x7FFF_00000000_00000001。
由于單精度和雙精度中J位隱式固定為1值,因此實際上也僅有擴展雙精度會出現unsupported編碼。
實驗1-2:打印各種編碼及信息
下面,我們做個測試,輸出x87 FPU的stack中的編碼值及狀態信息,代碼如下。
代碼清單1-5(topic01\ex1-2\protected.asm):
finit ; 初始化 x87 FPU fld TWORD [QNaN] ; 加載 QNaN 數 fld TWORD [SNaN] ; 加載 SNaN 數 fld TWORD [denormal] ; 加載 denormal 數 fld TWORD [infinity] ; 加載 infinity 數 fld TWORD [unsupported] ; 加載 unsupported 數 fldz ; 加載 0 值 fld1 ; 加載 1.0 值 call dump_data_register ; 打印信息
關于這個例子的代碼,請參考第20章的相關內容。
上面是運行在Bochs里的結果圖:這里顯示了x87 FPU的stack寄存器的8種狀態,除empty狀態的編碼值是一個indefinite(不確定)值外,其余數的編碼都是正確的。
1.2.5 浮點數精度的轉換
在x86/x64體系里,由于x87 FPU硬件使用擴展雙精度格式,因此必然會遇到single/double precision格式與double extended-precision格式之間的互換問題。
轉換為擴展雙精度數
當由單精度數或雙精度轉換為擴展雙精度數時,exponent部分必須基于擴展雙精度數的biased碼來調整。于是擴展雙精度數的exponent值為:
① 從單精度轉化:exponent–127+16383。
② 從雙精度轉化:exponent–1023+16383。
而擴展雙精度數的significand部分,由單/雙精度數的significand部分移植過來。
以單精度數1.11...×2120為例,它轉換為擴展雙精度的過程如下所示。
單精度數1.11...×2120的編碼值為0x7BFFFFFF,它的exponent值為0xF7(11110111B),significand部分全為1值。
于是擴展雙精度數的exponent值為0xF7-127+16383=0x4077(1000000001110111B),單精度23位的significand部分直接移到擴展雙精度的bit62到bit40位,低40位補0。
最終的擴展雙精度編碼值為0x4077_FFFFFF00_00000000。而對于雙精度數來說:52位的significand部分將直接移到擴展雙精度的bit62到bit11位。
擴展雙精度數轉換為單精度數
而從擴展雙精度轉換為單/雙精度數的情形會復雜得多,涉及目標格式的precison(精度)問題。當擴展雙精度significand部分的值超出目標格式的精度時,就會發生rounded(舍入)操作,從而引發precision異常。
要檢查超出精度的significand是否為0值,如下所示。
這部分不為0值時,就會發生rounded操作。
下面,我們以擴展雙精度數1.11...×2120轉化為單精度格式為例進行描述。當1.11...×2120為擴展雙精度格式時,它的編碼值為0x4077_FFFFFFFF_FFFFFFFF。
目標格式exponent部分的計算如下。
① 單精度數:exponent-16383+127。
② 雙精度數:exponent-16383+1023。
這個轉換過程較為復雜,如下所示。
圖中的陰影部分是超出精度的significand部分(bit 39~bit 0),它的值不為0,需要進行rounded操作,在x87 FPU中這個舍入依賴于rounded控制位。
IEEE754定義了以下4種舍入模式。
① round to nearest模式:朝±∞(正和負方向的無窮大值)方向舍入。
② round down模式:正數朝最大normal值舍入,負數朝-∞方向舍入。
③ round up模式:正數朝+∞方向舍入,負數朝最大normal值舍入。
④ round zero模式:正數和負數都朝最大normal值舍入。
上圖中的舍入是朝+∞方向舍入,如圖所示:bit 39的值為1,它將向bit 40進行舍入,效果等于+1值。目標格式中的significand部分舍入的結果值為0。
目標格式的exponent部分為擴展雙精度的exponent-16383+127=0xF7(11110111B),可是由于significand部分還是進位值,因此目標格式的最終exponent部分為0xF8(加上1值)。
因此,最終轉換的單精度值為0x7C000000,轉換得到的浮點數是1.0...×2121,結果大于原來的擴展雙精度浮點數。
擴展雙精度數轉換為雙精度數
這和轉換為單精度數是一致的。在雙精度格式里,它的精度是52位,因此超出精度部分為bit10到bit0位。
exponent的計算是擴展雙精度的exponent-16383+1023。
1.2.6 浮點數的溢出
1.2.5節所探討的精度轉換處于目標格式的finite(有限的)表達范圍內,這是一種正常的轉換行為。然而在某些時候會遇到目標格式不能表達的結果值,產生溢出。
在浮點數里,溢出分為兩種。
① overflow(向上溢出):結果值超出了目標格式的最大normal值(即finite范圍外)。
② underflow(向下溢出):結果值超出了目標格式的最小normal值(即tiny值或denormal數)。
上圖是一個實數軸上的分布圖,denormal(tiny)接近于0值,infinite(無窮大)數接近于NaN數,因此overflow和underflow溢出的條件如下。
在這里符號位不重要,正或負都能產生溢出。
1.2.6.1 underflow(向下溢出)
在x86/x64體系里,x87 FPU指令的rounded(舍入)結果值發生underflow時會產生#U(underflow)異常,并在x87 FPU的status寄存器里記錄下來。
從前面所述我們知道,結果值達到tiny值時就表示發生了underflow溢出,各種精度tiny值如下所示。
這個將要達到tiny的臨界值,就是目標精度的normal的最小值,當小于這個最小值時,就發生underflow溢出,對于underflow的處理,如下所示。
下面我們還是看看擴展雙精度數轉換為單精度格式。
例子:將擴展雙精度值0x3F80_C0000300_00000000轉換為單精度值。
這個值表示1.100...0110...000×2-127,它的bit63、bit62、bit41和bit 40位都為1值,如圖所示。
這個轉換過程中會對underflow進行如下處理。
判斷exponent是否溢出
單精度normal數的指數部應有-126<=X<128,也就是說,在二進制編碼值中exponent的值應為1<=exponent<255。關于normal的finite(有限)范圍請參考1.2.3節的normal數的描述。
由于這個值的指數部分為-127,因此對于單精度格式來說,它已經是underflow溢出了。
significand右移
在發生underflow的情況下,significand部分需要向右移位。下面就是一個significand右移的例子。
右移位數的計算方法是:指數的絕對值減去-126的絕對值(即減去126)。在本例里是向右移動一位,移位后的significand部分變成0.110...0110...000。
如下所示:目標單精度格式的exponent值將為0值,significand部分(包括J位)右移1位后,J位將變為0值(這是一個denormal數)。
精度舍入
如果significand的超出精度部分不為0值,同樣會發生rounded(舍入)操作。
如上所示:bit 39位向bit 40進行舍入,效果等于加上1值。0值將會寫入目標單精度值的exponent部分。最終得到的目標單精度值為0x00600002。
在x87 FPU里這個操作會引發#U(underflow)異常和#P(precision)異常,關于#U和#P異常的更多信息,請參考20.2.6節的相關描述。
1.2.6.2 overflow(向上溢出)
當結果值達到infinite(無限或無窮大)數時,就會產生overflow溢出。在x87 FPU中,當伴隨著目標格式的precision(精度)不能表達目標值時,就會產生#P(precision)異常和#O(overflow)異常。
如上所示:infinite值是一個overflow的臨界值,等于或大于這個值時就發生overflow。因此,當將擴展雙精度數1.0×2128轉換為單精度,或者將擴展雙精度數1.0×21024轉換為雙精度數時,就會產生overflow。
1.2.7 BCD碼
在BCD碼中,一個十進制數的每一位,使用8位的二進制進行編碼。以十進制數15為例,它的BCD編碼值為15H,如下所示。
每個BCD碼用1個字節來表示,這是非壓縮的BCD碼形式。BCD可以使用在GPI(通用指令)里,如下所示。
mov ax,9 ; AX 為 9(BCD 9) mov bx,8 ; BX 為 8(BCD 8) add ax,bx ; AX 值為11H aaa ; 執行 BCD 調整后,AX 值為 0107H(BCD 17)
在使用ADD指令相加后(假如兩個BCD碼相加),AAA指令將AX寄存器調整為非壓縮的BCD碼,AX結果值為0107H(即表示非壓縮的BCD碼17)。
packed BCD(壓縮的BCD碼)
非壓縮的BCD碼浪費了一半的空間,在packed BCD碼里,每個BCD數字使用4位來表示,如上圖所示。
packed BCD碼能使用在x87 FPU指令里。FBLD指令能加載一個80位寬的packed BCD碼進入x87 FPU數據寄存器里,這個80位的值可容納18個packed BCD碼。
1.2.8 SIMD數據
在SSE系列指令(SSE到SSE4.2)以及AVX指令里處理的數據分為以下兩大類。
① vector(packed)與scalar類型的浮點數據。
② packed integer數據。
這些數據類型使得SIMD指令能一次性處理多個數據,加大吞吐量。
128位與256位的vector floating-point數據
從SSE到SSE4.2指令集里,使用128位的XMM寄存器,支持128位的vector(矢量)浮點數據。而在VAX和FMA指令里增加到了256位的YMM寄存器,可以使用256位的vector數據。
如下所示:在128位vector數據里,可以容納4個單精度浮點數或者2個雙精度浮點數。在VAX和FMA指令上的YMM寄存器里可以容納8個單精度浮點數或者4個雙精度浮點數。
128位的scalar floating-point數據
在SSE系列指令和AVX/FMA指令里處理的scalar數據是128位寬,如下所示。
在128位的scalar數據里,單精度scalar只使用低32位,雙精度scalar只使用低64位,高位不作為數據進行運算。
128位與256位的packed integer數據
在SSE系列指令和AVX指令里處理的packed integer數據也是128位寬,包括以下幾種。
① packed byte。
② packed word。
③ packed doubleword。
④ packed quadword。
多數情況下,這些整型數也區分unsigned和signed版本,由對應的SIMD指令使用。然而Intel在Ivy Bridge微架構里增加了AVX2指令,AVX2指令能夠處理256位的packed integer數據。
如下所示,128位的packed integer數據能容納16個byte,8個word,4個doubleword或者2個quadword整型值。
當使用AVX2指令(AVX的擴展指令集)時,還可以使用256位的packed integer數據,能容納32個byte,16個word,8個doubleword或者4個quadword整型值。
- Mastering Spark for Data Science
- 21天學通PHP
- 80x86/Pentium微型計算機原理及應用
- Embedded Programming with Modern C++ Cookbook
- 網絡安全與防護
- 項目管理成功利器Project 2007全程解析
- 單片機技術一學就會
- Dreamweaver CS6中文版多功能教材
- 嵌入式操作系統原理及應用
- R Machine Learning Projects
- Learning Apache Apex
- 工業機器人力覺視覺控制高級應用
- Creating ELearning Games with Unity
- 中國戰略性新興產業研究與發展·數控系統
- 算法設計與分析