本文主要是介绍FPU (2) 一元二次方程式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Ch 23 FPU (2) 一元二次方程式
這一章裏小木偶將延續上一章的指令,寫一個計算一元二次方程式的程式,ROOT.ASM。
一元二次方程式之根
底下小木偶就利用以上所提的 8087 指令求得一元二次方程式之根。根據國中所學的,假如有一個一元二次方程式 ax2+bx+c=0,則此方程式 x 之解為
底下這個程式是求 x2-25=0 方程式之解,如果您要求其他方程式的解,必須修改原始程式之的 lr_a、lr_b、lr_c 三係數 (在 MASM 6.x 裡,c 是保留字,所以為了使 MASM 5.x 與 6.x 都能順利組譯,我在係數之前加上 lr,表示長實數之意)。這個程式也只能由 SYMDEB.EXE 觀看結果,小木偶將於稍後再撰寫直接顯示 ST 之十進位數值於螢幕上的副程式。
;***************************************
code segment
assume cs:code,ds:code
org 100h
;---------------------------------------
start: finit ;---st--;-st(1)-;-st(2)-;-st(3)-;06
fld lr_b ; b ;
fmul lr_b ; b2 ; ; ; ;08
fld lr_a ; a ; b2 ;
fmul lr_c ; ac ; b2 ; ; ;10
fimul const ; 4ac ; b2 ;
fsubp st(1),st ;D=bb-4ac ; ; ;12
shr const,1
fsqrt ; SQR(D); ; ; ;14
fld lr_b ; b ; SQR(D)
fchs ; -b ; SQR(D); ; ;16
fld st ; -b ; -b ; SQR(D)
fadd st,st(2) ;-b+SQ(D) -b ; SQR(D); ;18
fild const ; 2 ;-b+SQ(D) -b ; SQR(D)
fmul lr_a ; 2a ;-b+SQ(D) -b ; SQR(D);20
fdiv st(1),st ; 2a ; x1 ; -b ; SQR(D)
fxch st(1) ; x1 ; 2a ; -b ; SQR(D);22
fstp x1 ; 2a ; -b ; SQR(D)
fxch st(1) ; -b ; 2a ; SQR(D); ;24
fsubrp st(2),st ; 2a ;-b-SQ(D)
fdivp st(1),st ; x2 ; ; ; ;26
fstp x2
int 20h ;28
org 160h ;29
lr_a dq 1.00 ;30
lr_b dq 0.00 ;31
lr_c dq -25.0 ;32
x1 dq ? ;33 其中之一根
x2 dq ? ;34 其中之一根
const dw 4 ;35
;---------------------------------------
code ends
;***************************************
end start
先來看看執行結果。
h:/homepage/source>e:symdeb root.com [Enter]
Microsoft (R) Symbolic Debug Utility Version 4.00
Copyright (C) Microsoft Corp 1984, 1985. All rights reserved.
Processor is [80286]
-G [Enter]
Program terminated normally (0)
-DL 160 L5 [Enter]
2117:0160 00 00 00 00 00 00 F0 3F +0.1E+1
2117:0168 00 00 00 00 00 00 00 00 +0.0E+0
2117:0170 00 00 00 00 00 00 39 C0 -0.25E+2
2117:0178 00 00 00 00 00 00 14 40 +0.5E+1
2117:0180 00 00 00 00 00 00 14 C0 -0.5E+1
-Q [Enter]
為了能夠方便觀察係數與根,所以小木偶在程式第 29 行加入『org 160h』,使得所有長實數變數能夠由位址 160H 開始,前三個長實數依次是二次方程式的係數,第四、五個長實數是兩根。在 SYMDEB 裏,『E』是表示 10 的冪方的意思,例如,位址 160H 的長實數是 +0.1E+1 就是表示 +0.1 乘以 10 的一次方,也就是 1 的意思。
至於為何我選在位址 160H 開始存放變數呢?其實 160H 這個位址的獲得經過假設再精確求得。該位址的限制是不能覆蓋程式碼,因程式不大,小木偶先假設位址為 1C0H,組譯連結後由 SYMDEB 觀察得知最後一行『INT 20H』在位址 157H,所以我再修改為 160H。
ROOT.ASM 程式有一個缺陷,假如 b2-4ac < 0,其平方根會產生錯誤,這時應使用 FPU 的比較指令進而產生條件跳躍,使程式轉向。
使 FPU 能條件跳躍
所謂條件跳躍就是比較兩數的大小,如果某數較大,就執行一段程式,若較小則執行另一段程式,類似這樣的跳躍稱為條件跳躍。在 FPU 的指令集並沒有能使 FPU 跳躍的指令,事實上 FPU 也無法改變 CPU 暫存器之值 (還記得要改變程式執行位址必須改變 CS:IP 之值),所以要產生 FPU 條件跳躍必須用間接方法。其步驟有四個:
- 先用 FPU 的比較指令改變 FPU 的狀態字組暫存器。
- 再用 FPU 的指令 FSTSW 把狀態字組存入一個記憶體變數裏。
- 將此記憶體變數存入 AH 暫存器,再用 CPU 指令 SAHF 指令存入 CPU 的旗標暫存器裏。此步驟使旗標暫存器之值等於狀態字組。
- 再用 CPU 指令 JL/JG/JE/JA/JB 來跳躍至正確處執行。
狀態字組
FPU 有五類暫存器,前章已介紹過最常用的堆疊暫存器,這裡將介紹狀態字組(status word)。顧名思義,狀態字組是一個 16 位元的暫存器,表示 FPU 的狀態,所謂狀態是指 FPU 是不是在忙碌中、是否除以零、是否無效運算、現在堆疊頂端是那一個堆疊暫存器等等,在此處我們要注意的是四個狀態碼位元,C0、C1、C2、C3。這些位 元分布如下圖:
比較指令:FCOM、FCOMP、FCOMPP、FICOM、FICOMP
標題所見的這些比較指令都是以堆疊頂為目的運算元,而來源運算元可以是記憶體變數或是其他的堆疊暫存器,例如
FCOM ST,ST(1)
FCOM ST,x
上述第一例是比較 ST 和 ST(1) 之數值,第二例是比較 ST 和記憶體變數 x 之數值,而這記憶體變數的形態可以是短實數和長實數。因為 FCOM 等指令均以 ST 為目的運算元,所以 ST 是可以省略不寫的,例如上述兩例,可以寫成下面的樣子,
FCOM ST(1)
FCOM x
結果是一樣的。假如 ST 的比較對象是 ST(1) 的話( 來源運算元是 ST(1) ),連 ST(1) 也可以省略。至於 FCOMP 和 FCOMPP 分別是比較後彈出一次和彈出兩次,這裡彈出的數會消失不見並沒有存入記憶體中,這點和 FSTP 不同。而 FICOM 和 FICOMP 是用來比較整數的,而這來源運算元整數形態可以是字組整數和短整數。
FPU 的比較指令會改變狀態字組的 C3 和 C0 位元,C3、C0 位元是在狀態字組的第八、14 位元,如上圖。比較後依 ST 和來源運算元的大小,C3 和 C0 設定方式如下表:
比較結果 C3 C0
----------------------------
ST>來源運算元 0 0
ST<來源運算元 0 1
ST=來源運算元 1 0
FSTSW 指令
FSTSW (store status word) 這個指令的功用是用來把狀態字組取出並存入AX暫存器或 16 位元長的記憶體變數裏,其語法是
FSTSW mem16
FSTSW AX
注意!FSTSW 之後所接的運算原可以是 AX 或 16 位元長的記憶體變數,前者只能用在 387 等級以上的 FPU;至於 16 位元長的記憶體變數則是 8087 就可以使用了。為什麼只能接 AX 而不可接其他 16 位元的暫存器呢?主要是為了下面 SAHF 指令能很方便的把 AH 暫存器內的資料移到旗標暫存器裏,否則 FPU 的運算元是不能使用 CPU 的暫存器。
SAHF 指令
SAHF 是 8088 指令集中的一個指令,並非 FPU 指令。這個指令是把 AH 暫存器中的值移到旗標暫存器的較低的 8 個位元,這 8 個位元只有第七、六、四、二、零位元有用,第五、三、一位元沒有使用,請參考附錄二8088 的旗標暫存器。
所以假如 AH 為 0100 0000B,則執行 SAHF 指令後,零旗標會被設為一,您可以預測看看下列程式片段將會有何結果?
mov ah,01000000b
sahf
jz zf_set
mov dx,offset mes1
jmp short print
zf_set: mov dx,offset mes2
print: mov ah,9
int 21h
mes2 db '零旗標已設定$'
mes1 db '零旗標未設定$'
這程式片段會顯示出『零旗標已設定』。所以事實上,跳躍指令其實是僅僅看旗標暫存器的設定值決定如何跳到那裡去執行,至於旗標暫存器如何設定,在跳躍指令執行時是不須理會的。
FPU 的狀態暫存器裏的 C3 和 C0 位元,經 FSTSW 指令傳到 16 位元記憶體變數,再接著移到 AX 暫存器裏時,AH 的第 6、0 位元就是表示 C3 和 C0 位元,您可以對照上面 8087 狀態字組與 8088 旗標暫存器兩張圖,發現 C3 恰好對應 ZF,C0 恰好對應 CF。之後再由 SAHF 指令把 AH 移到旗標暫存器,所以 C3、C0 就移到零旗標與進位旗標了,只要檢查這兩個旗標就可以比較 ST 與來源運算元的大小,而決定跳躍方向。
修改 ROOT.ASM 程式
前面所撰寫的求一元二次方程式兩根的程式 ROOT.ASM 有缺陷,在這裡小木偶將他修改如果判別式小於零,程式能轉向執行。修改之後程式如下:
;***************************************
code segment
assume cs:code,ds:code
org 100h
;---------------------------------------
start: finit ;---st--;-st(1)-;-st(2)-;-st(3)-;06
fld lr_b ; b ;
fmul lr_b ; b^2 ; ; ; ;08
fld lr_a ; a ; b^2 ;
fmul lr_c ; ac ; b^2 ; ; ;10
fimul const ; 4ac ; b^2 ;
fsubp st(1),st ;D=bb-4ac ; ; ;12
shr const,1
fcom zero ;判別式是否小於 0 14
fstsw status ;結果存於 status 變數裏 15
fwait ;等待儲存完畢 16
mov ax,status ;比較結果移入 AX 17
sahf ;比較結果移入旗標暫存器 18
jb d_less_0 ;若小於則跳躍到 d_less_0 處 19
fsqrt ; SQR(D); ; ; ;20
fld lr_b ; b ; SQR(D)
fchs ; -b ; SQR(D); ; ;22
fld st ; -b ; -b ; SQR(D)
fadd st,st(2) ;-b+SQ(D) -b ; SQR(D); ;24
fild const ; 2 ;-b+SQ(D) -b ; SQR(D)
fmul lr_a ; 2a ;-b+SQ(D) -b ; SQR(D);26
fdiv st(1),st ; 2a ; x1 ; -b ; SQR(D)
fxch st(1) ; x1 ; 2a ; -b ; SQR(D);28
fstp x1 ; 2a ; -b ; SQR(D)
fxch st(1) ; -b ; 2a ; SQR(D); ;30
fsubrp st(2),st ; 2a ;-b-SQ(D)
fdivp st(1),st ; x2 ; ; ; ;32
fstp x2
int 20h ;34 結束程式
d_less_0: ;35 判別式小於零,無實數解
mov dx,offset message ;36 印出無實數解
mov ah,9
int 21h
int 20h ;39 結束程式
org 1e0h ;41
lr_a dq 1.00 ;42
lr_b dq 0.00 ;43
lr_c dq 25.0 ;44
x1 dq ? ;45 其中之一根
x2 dq ? ;46 其中之一根
zero dq 0 ;47
const dw 4 ;48
status dw ? ;49 狀態字組
message db '無實數解$'
;---------------------------------------
code ends
;***************************************
end start
將他存為 ROOT2.ASM,並組譯成 ROOT2.COM 檔,在 SYMDEB.EXE 裏執行並觀察結果。如果判別式為負值,則停止運算而印出『無實數解』後終止程式。
修改後的程式增加了幾行,第 14 到第 19 行,也就是在做開平方根運算前先檢查 ST 是否小於零,此時 ST 內的數值就是所謂的判別式 (b2-4ac)。值得注意的是第 16 行,小木偶加上了 FWAIT 指令,確保 FPU 已經把狀態字組存入 status 變數後,CPU 才將 status 之值存入 AX 裏。小木偶也試過如果把這行拿掉,似乎也能正確執行。
常數指令
FPU 裏有 7 個指令是用來把常用的常數推入堆疊頂稱為『常數指令』。這些常數都是內建在 FPU 裏的數值,都屬於暫時實數,其有效位數達 19 位數,當程式需要用到時可以很快的載入,節省時間與記憶體。這 7 個常數指令介紹如下:
FLD1
載入 1.0。( 把 1.0 推入堆疊頂 )
FLDZ
載入零。( 把 0.0 推入堆疊頂 )
FLDPI
載入圓周率,3.141592653589793239。( 把 π 推入堆疊頂 )
FLDL2T
載入 Log210。( 把 Log210 推入堆疊頂 )
FLDL2E
載入 Log2e,e 是自然對數的底數。( 把 Log2e 推入堆疊頂 )
FLDLG2
載入 Log102。( 把 Log102 推入堆疊頂 )
FLDLN2
載入 Loge2,事實上 Loge2 等於 Ln 2。( 把 Loge2 推入堆疊頂 )
註一:事實上有另一個指令可以直接比較 ST 和零,這個指令是 FTST (可以記成 TeST)。
FTST 指令
FTST 後不須接任何運算元,比較後狀態字組的設定方式和 FCOM 相同:
比較結果 C3 C0
----------------------------
ST>0 0 0
ST<0 0 1
ST=0 1 0
回到首頁, 到第二十二章, 到第二十四章
这篇关于FPU (2) 一元二次方程式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!