FPU (1) 简介

来源:互联网 发布:大数据概念 编辑:程序博客网 时间:2024/06/06 03:37

 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)、例外指標 (exceptionpointer)。雖然看起來很複雜,但是最重要且最常用的是堆疊暫存器。

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 的後面,您可以將它看成 integerload 的意思,而要推入的整數型態則是由『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 大類:資料傳輸(datatransfer)指令、算術指令、超越函數(transcendental)指令、常數(constant)指令、比較(comparison)指令、處理機控制(processor control)指令。

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

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

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

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

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

  • 實數且彈出(如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