FPU (1) 简介

 FPU 简介

FPU 是什麼

FPU 稱為浮點運算器是 floating-point processor unit 的縮寫它是一個處理數學運算的晶片。早在 1979 年英特爾就為了搭配 8086/8088 開發出一個名為 8087 的 FPU,IBM/PC/XT 個人電腦中的主機板上面,在 CPU 插槽附近有一個空著的插槽,就是給 FPU 用的。假如您去買 FPU,可以把 FPU 插在上面,再調整組態開關,就可使用 FPU 了。到了 80286、80386 時代也有與之配合的 FPU,分別稱為 80287、80387。到了 80486 時代,Intel 更把 FPU 整合到 80486 裡面變成單一晶片 ( 那時也有不含 FPU 的 80486,稱之為 SX,而含有 FPU 的稱為 DX)。到了 Pentuim 的時代,FPU 已經完全整合到 CPU 中,使用者完全感覺不到它的存在了。

講了這麼多,到底這個浮點運算器是幹什麼用的呢?小木偶想我們花了很多時間在講各種問題的處理上,這些都會牽涉到數的計算,直到目前為止,都停留在 整數的計算,而帶有小數、很大或很小的數,或者我們想計算三角函數、指數、對數時,一般的 CPU 是不能用一條指令就計算出來,常用的方法是以軟體模擬計算,這種模擬常要花很多時間同時也增加程式碼。所以 Intel 就特別設計了一個處理器,專門做這些數值的計算,有別於整數所以稱之為浮點運算器,因為是輔助 CPU 運算的所以也有人稱為輔助運算器、共同處理器 (coprocessor) 或是數值資料處理器 (NDP,numberic data processor) 等等。FPU 是採用硬體來計算浮點數、對數、三角函數等複雜運算,所以速度比用 CPU 以軟體模擬還快且精確許多,而且程式碼也小很多。

我想,由上面的說明,您應當瞭解 CPU 和 FPU 所處理的事不一樣,FPU 和 CPU 各有各的指令集,彼此不互相干擾。當 CPU 由記憶體提取指令時,如果發現這個指令是屬於 FPU 的指令,就將該指令所需要的位址計算好,交由 FPU 去處理,而 CPU 就接著去處理下一道指令,所以 CPU 和 FPU 能夠同步運算。但是這裡出現兩個問題,第一,如果下一道指令恰好要用到上一道 FPU 所計算的結果,這時就會產生錯誤;第二,在 FPU 運算結束之前,不能再執行下一道 FPU 指令。程式設計師有責任要注意到第一種錯誤是否可能發生,為保證同步運算,在兩個相連的 FPU 和 CPU 指令且同時存取相同的記憶體位址時,得在 FPU 指令前加上 WAIT 指令,而第二種錯誤的避免責任由組譯器負責,MASM 會在每一條 FPU 指令前自動加上 WAIT 這個指令,以保證與 CPU 能同步。

WAIT/FWAIT 指令

WAIT 指令就是使 CPU 等候 FPU 執行完成的指令,也可以寫成 FWAIT。組譯器會自動在每條 FPU 指令前加上 FWAIT 指令。

FPU 暫存器

FPU 的暫存器可分為五類,堆疊暫存器 (register stack)、狀態字組 (status word)、控制字組 (control word)、標籤字組 (tag word)、例外指標 (exception pointer)。雖然看起來很複雜,但是最重要且最常用的是堆疊暫存器。

FPU 共有八個堆疊暫存器,分別是 ST、ST(1)、ST(2)、ST(3)……ST(7),這八個暫存器每一個都是 80 位元,用來存放運算時所需要的資料,這和以前我們所說的堆疊所存的資料不太相同,但是操作方式卻是一樣的。FPU 許多運算都是先把數值推入堆疊頂端的 ST 暫存器,再對 ST 暫存器作運算。

這 8 個堆疊暫存器運作方式如同自助餐廳堆在起一堆的餐盤,當服務生堆上一個新餐盤,原來露在最上面的餐盤就變成第二個,第一個變成新餐盤,並且露在最上面;當 取出最上面的餐盤,其餘就都往上移一位置,原來第二個餐盤就露出來了。以術語來說,資料存放在堆疊稱為推入 (push),移出頂端的資料稱為彈出 (pop),但是在 FPU 實際運用上並不是真的把數值移到上或下一個堆疊暫存器,而是以一個指標來表示那一個堆疊暫存器在頂端,同時 FPU 也允許存取底下的堆疊,不像餐盤只能拿取最上面的餐盤。

以下圖說明,一開始堆疊是空的,首先把 987654 推入堆疊頂,所以 ST 為 987654,其餘仍是空的;第二步把 123456 推入堆疊,於是 ST 變為 123456,原先的 987654 被移到 ST(1),其餘仍是空的;第三步取出堆疊頂的資料,123456 被移到其他地方(記憶體某處),987654 被移到 ST,而 ST(7) 會移進一個空的資料。

NDP 堆疊暫存器運作方式

第一個 FPU 組合語言程式

原始程式

好了,小木偶想先簡單寫一個程式來介紹如何操作 FPU 的堆疊暫存器。底下小木偶介紹一個簡單的程式可以直接計算 32 位元的整數加法程式,但是為了將注意力集中在 FPU 的堆疊暫存器上,所以執行結果必須用 DEBUG.EXE 來觀察。

;***************************************
code segment
assume cs:code,ds:code
org 100h
;---------------------------------------
start: jmp short begin
n1 dd 987654 ;07 被加數
n2 dd 123456 ;08 加數
sum dt ? ;09 聚集的 BCD 數
begin: finit ; st ; st(1) ; st(2) ;10
fild n1 ; 987654; ; ;11
fild n2 ; 123456; 987654; ;12
fadd ;1111110; ; ;13
fbstp sum ; ; ; ;14
int 20h
;---------------------------------------
code ends
;***************************************
end start

短整數與聚集 BCD 數

FPU 可以接受七種型態的數值,在此程式裏只用兩種:短整數 (或短式整數,short integer)與聚集的 BCD 數。短整數是由雙字組 (您也可以說 32 個位元) 組成,它是以 2 的補數方式表示整數,其範圍為 -2x109 到 +2x109,它相當於 BASIC 的單精確度資料型態,在組合語言原始碼裏用『DD』定義,程式的第 7﹑8 行就定義了兩個短整數,n1 與 n2。

聚集的 BCD 數是由十個位元組組成,可以表示 18 位的整數,最高位址的那一個位元組的第 07 個位元表示此數的正負值,如果該位元為一表負值,零表正值,第 0 到 6 位元則未使用,在組合語言原始碼裏用『DT』來定義,DT 的意思是 define ten bytes,程式第 9 行就定義了一個聚集的 BCD 數。

現在來看看這個程式中所用到的幾個 FPU 指令,您可以看到四個新的指令,它們都是以『F』開頭的,事實上,凡是 FPU 的指令都是以『F』開頭。

FINIT 指令

FINIT 的功能就是把 FPU 重設,會把清除堆疊暫存器,所有的忙碌中斷,例外中斷旗標,一般要使用 FPU 時通常都會先用 FINIT 來重設。

FILD 指令

這個指令是用來把整數推入堆疊暫存器內 (載入整數到堆疊暫存器),至於要推入的整數則寫在 FILD 的後面,您可以將它看成 integer load 的意思,而要推入的整數型態則是由『DW』﹑『DD』﹑『DQ』定義,這三個定義分別定義字組整數、短整數、長整數。其語法是

FILD    來源運算元

FADD 指令

這是把目的運算元 (直接接在指令後的變數或堆疊暫存器) 與來源運算元 (接在目的運算元後的變數或堆疊暫存器) 相加,並將結果存入目的運算元,它有三種格式:

  • 指定兩個運算元,則其中一個一定要是 ST 暫存器,另一個也要是堆疊暫存器,FADD 會把來源運算元與目的運算元相加,並將結果存入目的運算元。例如:
    FADD    ST,ST(2)
    這個例子是把 ST 加上 ST(2) 後再存回 ST 中,所以 ST 值會改變而 ST(2) 之值不變。
  • 指定一個運算元,則此運算元表示來源運算元,而且必須是短實數或長實數其中之一的變數,FADD 會把來源運算元加上 ST,並存入 ST。例如:
    FADD    mem
    這個例子事實上和 FADD ST,mem 一樣。

  • 不指定運算元,就如同本程式,則 FADD 會把 ST(1) 加上 ST 並將和存入 ST(1),再彈出 ST 暫存器,所以最後的結果是 ST 等於原 ST 加上原 ST(1),並且還有彈出動作,這是初學者常犯的錯誤。(參考 FADDP 指令)

FBSTP 指令

這是把堆疊頂以聚集的 BCD 數彈出到接在後面的目的運算元,這個目的運算元必須是用『DT』定義的聚集 BCD 數。這個指令您可以記成 BCD store and pop,很明顯的 FBSTP 中的『ST』是 store 之意,P 是 pop 之意,其語法是:

FBSTP   目的運算元

以 DEBUG 觀察

小木偶將此程式命名為 FPU1.ASM,並將它變成 FPU1.COM 執行檔,用 DEBUG 看看:

H:/HomePage/SOURCE>debug fpu1.com [Enter]
-d 100 L20 [Enter]
1F90:0100 EB 12 06 12 0F 00 40 E2-01 00 00 00 00 00 00 00 ......@.........
1F90:0110 00 00 00 00 9B DB E3 9B-DB 06 02 01 9B DB 06 06 ................
-r [Enter]
AX=0000 BX=0000 CX=002B DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=1F90 ES=1F90 SS=1F90 CS=1F90 IP=0100 NV UP EI PL NZ NA PO NC
1F90:0100 EB12 JMP 0114
-t [Enter]

AX=0000 BX=0000 CX=002B DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=1F90 ES=1F90 SS=1F90 CS=1F90 IP=0114 NV UP EI PL NZ NA PO NC
1F90:0114 9B WAIT
-u 114 129 [Enter]
1F90:0114 9B WAIT
1F90:0115 DBE3 FINIT
1F90:0117 9B WAIT
1F90:0118 DB060201 FILD DWORD PTR [0102]
1F90:011C 9B WAIT
1F90:011D DB060601 FILD DWORD PTR [0106]
1F90:0121 9B WAIT
1F90:0122 DEC1 FADDP ST(1),ST
1F90:0124 9B WAIT
1F90:0125 DF360A01 FBSTP TBYTE PTR [010A]
1F90:0129 CD20 INT 20

您可以看到,在原始程式裏小木偶並沒有使用 WAIT 指令,但是 MASM 會自動在每一行 FPU 指令前加上去,而您可能發現 FADD 指令被 MASM 換成 FADDP ST(1),ST 了,怎麼會這樣呢?我想當我解釋完 FADDP 指令您就會釋疑了。

FADDP 指令

這個指令是使目的運算元加上 ST 暫存器,並彈出 ST 暫存器,而目的運算元必須是堆疊暫存器的其中之一,最後不管目的運算元為何,經彈出一次後,目的運算元會變成上一個堆疊暫存器了。其語法為:

FADDP   ST(?),ST

所以 FADDP ST(1),ST 結果和 FADD 指令省略所有運算元時是一樣的。

在上面用白色字表示的數值為 0F1206,這個數當然是十六進位整數,也就是程式中定義的 n1,您可以以筆算看看,是不是就是 987654?如果您已經不記得怎麼計算,請參考附錄一

用 DEBUG 來追蹤這個程式並無意義,因為 DEBUG 無法觀察 FPU 的暫存器,所以小木偶直接執行到程式尾端,觀察經過 FBSTP 運算後的 sum 變數:

-g 129 [Enter]

AX=0000 BX=0000 CX=002B DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=1F90 ES=1F90 SS=1F90 CS=1F90 IP=0129 NV UP EI PL NZ NA PO NC
1F90:0129 CD20 INT 20
-d 100 L20 [Enter]
1F90:0100 EB 12 06 12 0F 00 40 E2-01 00 10 11 11 01 00 00 ......@.........
1F90:0110 00 00 00 00
9B DB E3 9B-DB 06 02 01 9B DB 06 06 ................

上面紅色的數值就是其結果,是不是和我們運算的一樣呢?


小數的加法

以 FPU 來計算整數,實在是大材小用,底下我們來看看 FPU 怎樣計算帶有小數的加法。

;***************************************
code segment
assume cs:code,ds:code
org 100h
;---------------------------------------
start: jmp short begin
n1 dd 10.25 ;07 被加數
n2 dd 2.33 ;08 加數
sum dd ? ;09 和
begin: finit ; st ; st(1) ; st(2) ;10
fld n1 ; 10.25 ; ; ;11
fld n2 ; 2.33 ; 10.25 ; ;12
fadd ; 12.58 ; ; ;13
fstp sum ; ; ; ;14
int 20h
;---------------------------------------
code ends
;***************************************
end start

這個程式小木偶命名為 FPU2.ASM,它和 FPU1.ASM 不同之處,僅在於載入與彈出的部分,載入小數或是很大很小的數 (帶有小數的數或以十的冪方表示的數稱為浮點數) 用 FLD 載入,不可用 FILD 否則 FPU 會自動將小數點後的數捨入。

FLD 指令

載入浮點數到 ST 暫存器。而要載入的數可以用『DD』、『DQ』﹑『DT』來定義。

FSTP 指令

這個指令是用來把 ST 的數以浮點數的方式彈出至後面接的變數裏,而這個變數必須是用『DD』、『DQ』、『DT』其中之一定義的。其語法是:

FSTP    變數名

我想初學者要注意的是載入整數要用 FILD,載入浮點數要用 FLD(註一),這點很重要,也是常犯的錯誤。好吧,現在用 DEBUG 載入觀察看看。

H:/HomePage/SOURCE>debug fpu2.com [Enter]
-r [Enter]
AX=0000 BX=0000 CX=0025 DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000
DS=128B ES=128B SS=128B CS=128B IP=0100 NV UP EI PL NZ NA PO NC
128B:0100 EB0C JMP 010E

先看看 n1、n2 組譯後變成什麼樣子?

-d 102 Lc [Enter]
128B:0100 00 00 24 41 B8 1E-15 40 00 00 00 00 ..$A...@....

Short Real 短實數

上面白色部分的就是 n1 組譯後的情形,變成 41 24 00 00 了,n2 則變成 40 15 1E B8,sum 是 00 00 00 00,怎麼會變成這樣呢?原來組譯器看到帶有小數點的數值 (浮點數) 會翻譯成 IEEE 格式,而不用十六進位來處理。用 『DD』定義的浮點數稱為『短實數』,短實數佔有 4 個位元組,共 32 個位元。這 32 位元的最高位元 (第 31 位元) 表示正負值 (sign),若為零表示此數為正數,為一表示此數是負數。第 23 到 30 位元這 8 個位元表示指數部分 (exponent),指數部分是以 2 為底數,但在做乘冪之前,指數還得減去基準數 (bias),短實數的基準數是 127。第 0 位元到第 22 位元是有效數部份 (significand),有效數部份是以 1 開始,依次減半的等比數列 1/2、1/4、1/8、1/16、1/32……的方式排列相加,因為 1 固定所以不表示,而從 1/2 開始。而最後短實數的數值是:

短實數 = (-1)sign×significand×2exponent

以 10.25 為例,組譯器翻譯成 IEEE 格式是 41 24 00 00 先變成二進位 0100 0001 0010 0100 0000 0000 0000 0000,第 31 位元(天藍色)為零表示正數,接下來的 8 個位元(白色)換成十進位是 130,減去基底數 127 等於 3,所以指數部分就是 23。而最後面的部分是有效數,小木偶將它排成直列來說明:

1 ==>              1 (固定值,不在IEEE格式表示出來)
0 ==>表示 1/2*0 = 0
1 ==>表示 1/4*0 = 0.25
0 ==>表示 1/8*0 = 0
0 ==>表示 1/16*0 = 0
1 ==>表示 1/32*1 = 0.03125
以下皆為零

最後 1+0.25+0.03125 為 1.28125,再乘以指數部份 23 即可得 10.25。

這種方法看起來很複雜,不過我們不需要知道如此瑣碎的事情,我們對浮點數只需要知道三件事,佔用位元組幾個,準確度多少,能表示的範圍多大,這些請看註三

-u 10e 123 [Enter]
128B:010E 9B WAIT
128B:010F DBE3 FINIT
128B:0111 9B WAIT
128B:0112 D9060201 FLD DWORD PTR [0102]
128B:0116 9B WAIT
128B:0117 D9060601 FLD DWORD PTR [0106]
128B:011B 9B WAIT
128B:011C DEC1 FADDP ST(1),ST
128B:011E 9B WAIT
128B:011F D91E0A01 FSTP DWORD PTR [010A]
128B:0123 CD20 INT 20
-g [Enter]

Program terminated normally
-d 102 Lc [Enter]
128B:0100 00 00 24 41 B8 1E-15 40 AE 47 49 41 ..$A...@.GIA

同樣的,計算完後仍以浮點數方式表示,見白色部份,為了方便,小木偶建議使用 SYMDEB.EXE 來除錯,雖然它無法觀看堆疊暫存器的數值,但是可以把短實數﹑長實數和暫時實數三種模式變成十進位的『科學記號』(有點和真正的科學記號出入) 顯示於螢光幕。底下用 SYMDEB 來看看:

H:/HomePage/SOURCE>symdeb fpu2.com [Enter]
Microsoft (R) Symbolic Debug Utility Version 4.00
Copyright (C) Microsoft Corp 1984, 1985. All rights reserved.

Processor is [80286]
-ds 102 L3 [Enter]
21FE:0102 00 00 24 41 +0.1025E+2
21FE:0106 B8 1E 15 40 +0.2329999923706055E+1
21FE:010A 00 00 00 00 +0.0E+0
-g [Enter]

Program terminated normally (0)
-ds 102 L3 [Enter]
21FE:0102 00 00 24 41 +0.1025E+2
21FE:0106 B8 1E 15 40 +0.2329999923706055E+1
21FE:010A AE 47 49 41 +0.1257999992370605E+2

SYMDEB 加強了 dump 指令,ds 就是用短實數方式顯示,dl (英文字母的L,不是阿拉伯數字的 1) 是以常實數方式顯示,可以參考附錄六。 您可以看到在 010A 處就是 FPU 的計算結果,您或許會說,怎麼不是 12.58?這是因為當把浮點數變成 IEEE 格式推入 FPU 堆疊暫存器時,會產生誤差,這是無可避免的,在 FPU 的堆疊暫存器裏僅有 80 位元,當然不能表示所有的數,所以會必定做一些捨入動作。

設計師所能做的是增加精確度而已,所以此處您應該注意兩件事。第一,您所寫的程式所需精確度為何?如果是不須太準確就用短實數,如果要求很高就用暫時實數。第二,儘量不要把堆疊暫存器的數推入彈出,只有必要時再做,因為這樣不但會降低精確度也浪費時間。

算數指令

在簡單介紹過 FPU 堆疊操作後,小木偶簡單介紹有關 80X87 四則運算指令。

加法指令:FADD﹑FADDP﹑FIADD

FPU 提供了三種加法指令,前兩種,FADD﹑FADDP 前面已敘述過,不再重複,此處僅介紹 FIADD 指令。顧名思義,『I』是指整數 (integer) 之意,FIADD 是把 ST 加上來源運算元,然後再存入 ST 暫存器,來源運算元必須是字組整數或短整數形態的變數。其語法是

FIADD   mem

mem 是字組整數或短整數形態的變數。

減法指令:FSUB﹑FSUBP﹑FSUBR﹑FSUBRP﹑FISUB﹑FISUBR

FPU 所提供的減法指令有六種:FSUB﹑FSUBP﹑FSUBR﹑FSUBRP﹑FISUB﹑FISUBR。第一個指令,FSUB 指令,它的用法和 FADD 相同,也有三種格式,分成指定兩個運算元﹑指定一個運算元和不指定運算元三種。第二個指令,FSUBP,它的用法和 FADDP 相同,所以這兩個指令就不再說明。

第三個指令,FSUBR 指令,它和 FSUB 只有一點不同,就是減數與被減數互換,這個『R』字是 reversed 的意思。它也有三種格式:

  • 指定兩個運算元,語法如下:
    FSUBR   x,y
    x,y 其中之一必須是 ST 暫存器,另外一個必須是其他的堆疊暫存器,FSUBR 會把以 y-x 所得之差存入 x 內。例如:
    FSUBR   ST(2),ST
    是把 ST-ST(2) 之值存入 ST(2) 裏。

  • 指定一個運算元,語法如下:
    FSUBR   mem
    此運算元一定要是記憶體變數,而且必須是短實數或長實數之一的實數形態,FSUBR 會把此變數之值減去 ST 暫存器再存到 ST 暫存器裏。

  • 不指定運算元,則表示把 ST 之值減去 ST(1),並把差存回 ST(1),再做一次彈出動作,所以最後 ST 之值為原來的 ST 減 ST(1)。

第四個指令,FSUBPR,它的用法和 FSUBP 相同,只有一點不同,就是減數與被減數互換。例如

FSUBPR  ST(1),ST

這個例子會把 ST-ST(1) 之差存入 ST(1),然後再做一次彈出動作,使得最後 ST 變成原來的 ST-ST(1)。

第五個指令,FISUB,它是整數減法指令,把 ST 減去來源運算元的差,再存入 ST 內,來源運算元必須是字組整數或短整數變數。

第六個指令是,FISUBR,它也是整數減法指令,它和 FISUB 指令相同,差別只在減數與被減數交換。

乘法指令:FMUL﹑FMULP﹑FIMUL

這三個指令和 FADD﹑FADDP﹑FIADD 相同,只是加法改成乘法而已。

除法指令:FDIV﹑FDIVP﹑FDIVR﹑FDIVRP﹑FIDIV﹑FIDIVR

這六個除法指令和減法指令 FSUB﹑FSUBP﹑FSUBR﹑FSUBRP﹑FISUB﹑FISUBR 相同,只是減法改成除法。

改變符號:FCHS

這個指令會改變 ST 的正負值,如果原先 ST 為正值,執行後變為負值;原先為負值,執行後為正值。

絕對值:FABS

把 ST 之值取出,取其絕對值後再存回去。

平方根:FSQRT

將 ST 之值取出,開根號後再存回去。

FSCALE 指令

這個指令是計算 ST*2ST(1)之值,再把結果存入 ST 裏而 ST(1) 之值不變。ST(1) 必須是在 -32768 到 32768 (-215 到 215 )之間的整數,如果超過這個範圍計算結果無法確定,如果不是整數 ST(1) 會先向零捨入成整數再計算。所以為安全起見,最好是由字組整數載入到 ST(1) 裏。

FRNDINT 指令

這個指令是把 ST 的數值捨入成整數,FPU 提供四種捨入方式,由 FPU 的控制字組(control word)中的 RC 兩個位元決定,如下表:

RC捨入控制說明例子
00四捨五入向最近的整數
逢四捨去,遇五進位
4.8 → 5.0  -4.8 →-5.0
4.2 → 4.0  -4.2 →-4.0
01向負無限大捨入正值捨去小數部分
負值捨去小數部分後再減一
4.8 → 4.0  -4.8 →-5.0
4.2 → 4.0  -4.2 →-5.0
10向正無限大捨入正值捨去小數部分後再加一
負值捨去小數部分
4.8 → 5.0  -4.8 →-4.0
4.2 → 5.0  -4.2 →-4.0
11向零捨去不論正負值均捨去小數部分4.8 → 4.0  -4.8 →-4.0
4.2 → 4.0  -4.2 →-4.0

FPREM 指令

這個指令是求部份餘數(partial remaimder),較簡略的說法是將 ST 除以 ST(1) 後的餘數存回 ST,ST(1) 則不變。這個指令實際運作時,是以連續減法的方式求出餘數,詳細情形在三角函數時說明。

FXTRACT 指令

這個指令稱為抽取指數與有效數(extract exponent and significand),是把 ST 內的數值改成 X*2Y,然後把 Y 存回 ST 裏,再把 X 推入堆疊,所以最後 ST 為有效數,ST(1) 為以 2 為底的指數。FXTRACT 與 FSCALE 恰好成相反運算。

整理

講了這麼多的算數指令,在此做個整理。8087 共有 68 個指令,分為 6 大類:資料傳輸(data transfer)指令、算術指令、超越函數(transcendental)指令、常數(constant)指令、比較(comparison)指令、 處理機控制(processor control)指令。

針對算術指令,8087 提供了 18 個有關四則運算的指令以及三個較常用的指令。這 18 個四則運算的指令基本格式都是像下面這樣

指令    目的運算元, 來源運算元

其操作過程都是把目的運算元和來源運算元做加、減、乘、除後再存回目的運算元,其中加法與乘法目的運算元和來源運算元互換並不影響結果,但減法與除 法則結果會不同,所以又分為兩種,標準減法是目的運算元減去來源運算元後再存回目的運算元,而『反』減法則是來源運算元減去目的運算元後再存回目的運算 元,除法也和減法相同,不再贅述。

這些四則運算指令依目的運算元及來源運算元的格式又可分為三種,:

  • 實數(如FADD、FSUB、FSUBR、FMUL、FDIV、FDIVR):可依後面的指定方式再分為指定兩個運算元、僅指定來源運算元和不指定運算元三種格式,請參考FADDFSUBR之說明。

  • 實數且彈出(如FADDP、FSUBP、FSUBRP、FMULP、FDIVP、FDIVRP):這類格式只能使用堆疊暫存器為運算元,而且 ST 固定為來源運算元,所以 ST 可省略不寫出來。

  • 整數(如FIADD、FISUB、FISUBR、FIMUL、FIDIV、FIDIVR):此類運算的來源運算元只能為字組整數或短整數的變數,不能是堆疊暫存器。目的運算元固定是 ST 暫存器,也可以省略不寫出來。


註一:事實上載入整數有兩種方法:用 FILD 和 FLD,它們的使用方法不同。假如用 FILD 載入,則必須用 DW、DD、DQ 三種方式宣告,並且其後的資料必須是沒有小數點或 E。(E 表示 10 的幾次方,這幾次方寫在 E 的後面)。假如用 FLD 載入,則用 DD、DQ 方式宣告,而其後的資料必須包含小數點或是 E。例如:

num1    dd      123456
num2 dd 123456.0
fild num1
fld num2

雖然 num1、num2 都是十二萬三千四百五十六的整數,但是經由 MASM 編碼後之結果不同,num1 被看成是十六進位整數,編碼成 01E240,num2 被看成是 IEEE 浮點格式,編碼成 00 20 F1 47,因此載入方法不同。

註二:事實上,浮點數的編碼方式有兩種,一種是 IEEE 格式,另一種是微軟自訂的『Microsoft 二進位格式』。在 MASM 第 5.0 版及其以後版本的浮點數,MASM 會自動編碼成 IEEE 格式;而在 MASM 4.0 及其以前的版本會自動使用『Microsoft 二進位格式』。要使用那一種編碼方式,可以在原始程式的第一行或第一個區段定義前面加上『.8087』或『.MSFLOAT』指示元,前者表示使用 IEEE 編碼,後者使用微軟二進位格式編碼。

換句話說,如果您是使用 MASM 5.0 及其以後的版本,不加『.8087』和加入『.8087』都會被編碼成 IEEE 格式,如果要使用『微軟二進位格式』的話必須加上『.MSFLOAT』指示元。反之,在 MASM 4.0 及其前版的組譯程式要使用 IEEE 格式編碼時必須加上『.8087』指示元,否則會使用微軟二進位格式。

另外還有『.80287』指示元是用來使用 80287 新增加的指令。

註三:80X87 所能接受的資料形態共有七種,分別是四種整數:字組整數﹑短整數﹑長整數﹑聚集 BCD 數;以及三種浮點數:短實數﹑長實數﹑暫時實數。

除了聚集 BCD 數外,整數均以十六進位來表示,只是長度不同而已,字組整數長 16 位元( 2 個位元組),用『DW』定義。短整數長 32 位元 ( 4 個位元組 ),用『DD』定義 (定義雙字組之意,define di-word)。長整數長 64 位元 ( 8 個位元組 ),用『DQ』定義 (定義四字組之意,define quart-word)。

短實數的說明,已經在前文說 明,在此不說明了。長實數與暫時實數的編排方式和短實數類似,只是指數部份和有效數部份不同而已。長實數以『DQ』定義,正負位元在第 63 位元,指數部份在第 52 到 62 位元,其餘為有效數部份,基準值為 1023。暫時實數以『DT』定義,正負位元在第 79 位元,指數部份在第 78 到 64 位元,其餘為有效數部份,基準值為 16383。暫時實數與長、短實數的有效數有一點不同,暫時實數的有效數是在第 63 位元,此位元是由 1 開始表示、下一個 ( 第 62 位元 ) 表示 1/2……;而長、短實數則分別在第 51、22 位元,此位元由 1/2 開始、下一個位元則是表示 1/4……。在 FPU 堆疊暫存器裏儲存的數都是暫時實數,即使用整數載入 FPU 也會將他轉換成暫時實數。底下是它們的說明圖:

8087 資料形態圖解

底下是它們的列表整理:

資料形態佔用位元組有效數元能表示範圍表示 -127
字組整數24-32768 到 32767FF 81
短整數49-2147483648 到 2147483647 的整數FF FF FF 81
長整數818-9×1018 到 9×1018FF FF FF FF FF FF FF 81
聚集 BCD 整數1018-999999999999999999 到 999999999999999999 的整數80 00 00 00 00 00 00 00 01 27
短實數46 或 710-37 到 1038C2 FE 00 00
長實數815 或 1610-307 到 10308C0 5F C0 00 00 00 00 00
暫時實數101910-4932 到 1.1897×104932
或-10-4932 到 -1.1897×104932
C0 05 FE 00 00 00 00 00 00 00
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值