5.9 匯編語(yǔ)言:浮點(diǎn)數(shù)操作指令
匯編語(yǔ)言是一種面向機(jī)器的低級(jí)語(yǔ)言,用于編寫(xiě)計(jì)算機(jī)程序。匯編語(yǔ)言與計(jì)算機(jī)機(jī)器語(yǔ)言非常接近,匯編語(yǔ)言程序可以使用符號(hào)、助記符等來(lái)代替機(jī)器語(yǔ)言的二進(jìn)制碼,但最終會(huì)被匯編器編譯成計(jì)算機(jī)可執(zhí)行的機(jī)器碼。
浮點(diǎn)運(yùn)算單元是從80486處理器開(kāi)始才被集成到CPU中的,該運(yùn)算單元被稱(chēng)為FPU浮點(diǎn)運(yùn)算模塊,F(xiàn)PU不使用CPU中的通用寄存器,其有自己的一套寄存器,被稱(chēng)為浮點(diǎn)數(shù)寄存器棧,F(xiàn)PU將浮點(diǎn)數(shù)從內(nèi)存中加載到寄存器棧中,完成計(jì)算后在回寫(xiě)到內(nèi)存中。
FPU有8個(gè)可獨(dú)立尋址的80位寄存器,分別名為`R0-R7`他們以堆棧的形式組織在一起,棧頂由FPU狀態(tài)字中的一個(gè)名為T(mén)OP的域組成,對(duì)寄存器的引用都是相對(duì)于棧頂而言的,棧頂通常也被叫做ST(0)最后一個(gè)棧底則被記作ST(7)其使用方式與堆棧一致。
浮點(diǎn)數(shù)運(yùn)算通常會(huì)使用一些更長(zhǎng)的數(shù)據(jù)類(lèi)型,如下就是MASM匯編器定義的常用數(shù)據(jù)類(lèi)型.
```ASM
.data
? var1 QWORD? 10.1? ? ; 64位整數(shù)
? var2 TBYTE? 10.1? ? ; 80位(10字節(jié))整數(shù)
? var3 REAL4? 10.2? ? ; 32位(4字節(jié))短實(shí)數(shù)
? var4 REAL8? 10.8? ? ; 64位(8字節(jié))長(zhǎng)實(shí)數(shù)
? var5 REAL10 10.10? ?; 80位(10字節(jié))擴(kuò)展實(shí)數(shù)
```
此外浮點(diǎn)數(shù)對(duì)于指令的命名規(guī)范也遵循一定的格式,浮點(diǎn)數(shù)指令總是以F開(kāi)頭,而指令的第二個(gè)字母則表示操作位數(shù),例如:B表示二十進(jìn)制操作數(shù),I表示二進(jìn)制整數(shù)操作,如果沒(méi)有指定則默認(rèn)則是針對(duì)實(shí)數(shù)的操作`fld`等.
### 9.1 FLD/FSTP
FLD 和 FSTP 是x86架構(gòu)處理器中的浮點(diǎn)操作指令,F(xiàn)LD指令用于將浮點(diǎn)數(shù)從內(nèi)存裝載進(jìn)浮點(diǎn)寄存器,或者FSTP指令從浮點(diǎn)寄存器存儲(chǔ)到內(nèi)存中。
FLD 指令用于從內(nèi)存中讀取單精度浮點(diǎn)數(shù)(32位)或雙精度浮點(diǎn)數(shù)(64位),并將其存儲(chǔ)到浮點(diǎn)棧中。FLD 指令的語(yǔ)法如下:
```ASM
FLD source
```
其中,source 可以是內(nèi)存地址、寄存器或立即數(shù)。例如,要將雙精度浮點(diǎn)數(shù)`3.14159`存儲(chǔ)到浮點(diǎn)棧中,可以使用以下指令:
```ASM
movsd xmm0, [pi]? ? ? ; 將pi常量的值放入xmm0寄存器中
movsd [esp], xmm0? ? ?; 將xmm0寄存器中的值存儲(chǔ)到棧頂
fld qword ptr [esp]? ?; 將棧頂?shù)闹祻膬?nèi)存中裝載到浮點(diǎn)棧中
```
其中,xmm0 是雙精度浮點(diǎn)寄存器,pi 是一個(gè)雙精度浮點(diǎn)常量的地址,esp 是堆棧指針寄存器,qword ptr標(biāo)記用于指示要讀取的內(nèi)存單元的數(shù)據(jù)大小。
FSTP 指令用于將浮點(diǎn)棧頂?shù)闹祻棾?,并將其存?chǔ)到內(nèi)存中。FSTP指令的語(yǔ)法如下:
```ASM
FSTP destination
```
其中,destination 可以是內(nèi)存地址、寄存器或立即數(shù)。例如,將浮點(diǎn)棧頂?shù)闹荡鎯?chǔ)到內(nèi)存單元 x 中,可以使用以下指令:
```ASM
fstp qword ptr [x]? ? ; 將浮點(diǎn)棧頂?shù)闹荡鎯?chǔ)到 x 變量的內(nèi)存單元中
```
需要注意,F(xiàn)STP 指令會(huì)將浮點(diǎn)棧頂部的值彈出,在棧頂?shù)闹当淮鎯?chǔ)到目標(biāo)地址之后,浮點(diǎn)棧頂部的指針將自動(dòng)下移。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? var1 QWORD 10.0
? var2 QWORD 20.0
? var3 QWORD 30.0
? var4 QWORD 40.0
? result QWORD ?
.code
? main PROC
? ? ; 初始化浮點(diǎn)單元
? ? finit
? ??
? ? ; 依次將數(shù)據(jù)入棧
? ? fld qword ptr ds:[var1]
? ? fld qword ptr ds:[var2]
? ? fld qword ptr ds:[var3]
? ? fld qword ptr ds:[var4]
? ??
? ? ; 獲取當(dāng)前ST(0)棧幀元素
? ? fst qword ptr ds:[result]
? ??
? ? ; 從棧中彈出元素
? ? fstp qword ptr ds:[result]
? ? fstp qword ptr ds:[result]
? ? fstp qword ptr ds:[result]
? ? fstp qword ptr ds:[result]
? ? int 3
? main ENDP
END main
```
壓棧指令同樣支持變址尋址的方式,如下代碼案例中我們可以通過(guò)循環(huán)將一個(gè)數(shù)組壓入浮點(diǎn)數(shù)寄存器,其中使用`FLD`指令時(shí)壓入一個(gè)浮點(diǎn)實(shí)數(shù),而`FILD`則是將實(shí)數(shù)轉(zhuǎn)換為雙精度浮點(diǎn)數(shù)后壓入堆棧。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? Array QWORD 10.0,20.0,30.0,40.0,50.0
? Count DWORD ?
? Result QWORD ?
.code
? main PROC
??
? ? ; 初始化浮點(diǎn)單元
? ? finit
? ? mov dword ptr ds:[Count],0
? ? jmp L1
? L2: mov eax,dword ptr ds:[Count]
? ? add eax,1
? ? mov dword ptr ds:[Count],eax
? L1: mov eax,dword ptr ds:[Count]
? ? cmp eax,5
? ? jge lop_end
? ??
? ? ; 使用此方式壓棧
? ? fld qword ptr ds:[Array + eax * 8]? ?; 壓入浮點(diǎn)實(shí)數(shù)
? ? fild qword ptr ds:[Array + eax * 8]? ; 壓入雙精度浮點(diǎn)數(shù)
? ? jmp L2
? lop_end:
? ? int 3
? main ENDP
END main
```
### 9.2 FCHS/FABS
FCHS 指令是x86架構(gòu)處理器中的浮點(diǎn)數(shù)操作指令,該指令可用于把`ST(0)`中值的符號(hào)變反,F(xiàn)ABS 指令用于將浮點(diǎn)數(shù)的值取絕對(duì)值。這兩條指令的操作對(duì)象是浮點(diǎn)寄存器,而不是浮點(diǎn)棧。因此,它并未涉及堆棧操作或操作數(shù)的傳遞。在使用 FCHS 和 FABS 指令時(shí),需要使用浮點(diǎn)操作指令前綴 F 來(lái)標(biāo)識(shí)它們是浮點(diǎn)數(shù)操作指令。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? Array QWORD 10.0,20.0,30.0,40.0,50.0
? Result QWORD ?
??
? szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0?
.code
? main PROC
? ? ; 初始化壓棧
? ? finit
? ? fld qword ptr ds:[Array]
? ? fld qword ptr ds:[Array + 8]
? ? fld qword ptr ds:[Array + 16]
? ? fld qword ptr ds:[Array + 24]
? ? fld qword ptr ds:[Array + 32]
? ? ; 對(duì)ST(0)數(shù)據(jù)取反 (不影響浮點(diǎn)堆棧)
? ? fchs? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?; 對(duì)ST(0)取反
? ? fchs? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?; 再次取反
? ? fst qword ptr ds:[Result]? ? ? ? ? ? ? ? ? ? ? ? ? ; 取ST(0)賦值到Result
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 循環(huán)將數(shù)組取反后回寫(xiě)如Array中
? ? mov ecx,5
? S1:
? ? fchs
? ? fstp qword ptr ds:[Array + ecx * 8]
? ? loop S1
? ??
? ? ; 讀入Array中的數(shù)據(jù)到ST寄存器
? ? mov ecx,5
? S2:
? ? fld qword ptr ds:[Array + ecx * 8]
? ? loop S2
? ??
? ? ; 通過(guò)FABS取絕對(duì)值,并反寫(xiě)會(huì)Array中
? ? mov ecx,5
? S3:
? ? fabs? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; 取ST(0)的絕對(duì)值
? ? fstp qword ptr ds:[Array + ecx * 8]? ?; 反寫(xiě)
? ? loop S3
? ??
? ? int 3
? main ENDP
END main
```
### 9.3 FADD/FADDP/FIADD
浮點(diǎn)數(shù)加法系列指令,該系列可分為`FADD/FADDP/FIADD`,這些指令分別針對(duì)不同的場(chǎng)景使用,此外還會(huì)區(qū)分無(wú)操作數(shù)模式,寄存器操作數(shù),內(nèi)存操作數(shù),整數(shù)相加等。這些指令用于不同的場(chǎng)景下進(jìn)行操作,如下所述:
FADD 指令用于將兩個(gè)浮點(diǎn)數(shù)相加,并將結(jié)果存儲(chǔ)到浮點(diǎn)寄存器中。FADD指令支持多種操作數(shù)類(lèi)型,包括無(wú)操作數(shù)模式、寄存器操作數(shù)和內(nèi)存操作數(shù)等。例如,將一個(gè)雙精度浮點(diǎn)數(shù)和一個(gè)32位整數(shù)相加,可以使用以下指令:
```ASM
fld qword ptr [x]? ? ; 將雙精度浮點(diǎn)數(shù)x裝載到棧頂
fiadd dword ptr [y]? ; 將32位整數(shù)y裝載到浮點(diǎn)寄存器中,并與棧頂?shù)母↑c(diǎn)數(shù)相加
fstp qword ptr [z]? ?; 將浮點(diǎn)棧頂?shù)闹荡鎯?chǔ)到雙精度浮點(diǎn)數(shù)z中
```
FADDP 指令也是用于將兩個(gè)浮點(diǎn)數(shù)相加,但是會(huì)將結(jié)果彈出并存儲(chǔ)到目標(biāo)寄存器或內(nèi)存中。`FADDP`指令與`FADD`指令最大的區(qū)別在于它彈出了浮點(diǎn)棧頂?shù)闹?。例如,將兩個(gè)單精度浮點(diǎn)數(shù)相加并將結(jié)果存儲(chǔ)到內(nèi)存中,可以使用以下指令:
```ASM
fld dword ptr [x]? ? ; 將單精度浮點(diǎn)數(shù)x1裝載到棧頂
fadd dword ptr [y]? ?; 將單精度浮點(diǎn)數(shù)x2裝載到棧頂,并與棧頂?shù)臄?shù)相加
fstp dword ptr [z]? ?; 將浮點(diǎn)棧頂?shù)闹荡鎯?chǔ)到單精度浮點(diǎn)數(shù)z中,同時(shí)彈出棧頂
```
FIADD 指令用于將一個(gè)整數(shù)加到浮點(diǎn)寄存器的值中。與`FADD`指令不同,其支持的數(shù)據(jù)類(lèi)型只有整數(shù)類(lèi)型,而沒(méi)有浮點(diǎn)數(shù)類(lèi)型。使用`FIADD`指令時(shí),要將操作數(shù)用一個(gè)寄存器或內(nèi)存地址表示。例如,將一個(gè)16位有符號(hào)整數(shù)加到浮點(diǎn)數(shù)中,可以使用以下指令:
```ASM
fild word ptr [x]? ?; 將16位有符號(hào)整數(shù)x裝載到浮點(diǎn)寄存器中
fadd dword ptr [y]? ; 將32位浮點(diǎn)數(shù)y裝載到棧頂,并與浮點(diǎn)寄存器中的整數(shù)相加
fstp dword ptr [z]? ; 將浮點(diǎn)棧頂?shù)闹荡鎯?chǔ)到雙精度浮點(diǎn)數(shù)z中
```
如下匯編代碼將分別總結(jié)四種不同的浮點(diǎn)數(shù)計(jì)算方式,讀者可自行根據(jù)提示信息理解這其中的含義。
?- 第一種:無(wú)操作數(shù)模式,執(zhí)行`FADD`時(shí),ST(0)寄存器和`ST(1)`寄存器相加后,結(jié)果臨時(shí)存儲(chǔ)在`ST(1)`中,然后將`ST(0)`彈出堆棧,最終結(jié)果就會(huì)存儲(chǔ)在棧頂部,使用`FST`指令即可取出來(lái)。
?- 第二種:則是兩個(gè)浮點(diǎn)寄存器相加,最后的結(jié)果會(huì)存儲(chǔ)在源操作數(shù)ST(0)中。
?- 第三種:則是內(nèi)存操作數(shù),就是ST寄存器與內(nèi)存相加。
?- 第四種:是與整數(shù)相加,默認(rèn)會(huì)將整數(shù)擴(kuò)展為雙精度,然后在于ST(0)相加。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? Array? QWORD 10.0,20.0,30.0,40.0,50.0
? IntA? ?DWORD 10
? Result QWORD ?
??
? szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0?
.code
? main PROC
? ? finit
? ? fld qword ptr ds:[Array]
? ? fld qword ptr ds:[Array + 8]
? ? fld qword ptr ds:[Array + 16]
? ? fld qword ptr ds:[Array + 24]
? ? fld qword ptr ds:[Array + 32]
? ??
? ? ; 第一種:無(wú)操作數(shù) fadd = faddp
? ? ;fadd
? ? ;faddp
? ??
? ? ; 第二種:兩個(gè)浮點(diǎn)寄存器相加
? ? fadd st(0),st(1)? ? ? ? ? ; st(0) = st(0) + st(1)
? ? fst qword ptr ds:[Result] ; 取出結(jié)果
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? fadd st(0),st(2)? ? ? ? ? ; st(0) = st(0) + st(2)
? ? fst qword ptr ds:[Result] ; 取出結(jié)果
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第三種:寄存器與內(nèi)存相加
? ? fadd qword ptr ds:[Array] ; st(0) = st(0) + Array
? ? fst qword ptr ds:[Result] ; 取出結(jié)果
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? fadd real8 ptr ds:[Array + 8]
? ? fst qword ptr ds:[Result] ; 取出結(jié)果
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第四種:與整數(shù)相加
? ? fiadd dword ptr ds:[IntA]
? ? fst qword ptr ds:[Result] ; 取出結(jié)果
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ? int 3
? main ENDP
END main
```
### 9.4 FSUB/FSUBP/FISUB
x86架構(gòu)處理器的浮點(diǎn)數(shù)減法指令有`FSUB/FSUBP/FISUB`該系列指令從目的操作數(shù)中減去原操作數(shù),把差存儲(chǔ)在目的操作數(shù)中,目的操作數(shù)必須是ST寄存器,源操作數(shù)可以是寄存器或內(nèi)存,運(yùn)算的過(guò)程與加法指令完全一致。
FSUB指令從浮點(diǎn)數(shù)寄存器或內(nèi)存中減去一個(gè)浮點(diǎn)數(shù),并將結(jié)果存儲(chǔ)到浮點(diǎn)寄存器中。語(yǔ)法如下:
```ASM
FSUB destination, source
```
其中, destination 表示目的寄存器或內(nèi)存地址,source 表示源寄存器或內(nèi)存地址。例如,要將浮點(diǎn)寄存器ST(0)中的值減去雙精度浮點(diǎn)數(shù) x ,并將結(jié)果存儲(chǔ)回ST(0),則可以使用以下指令:
```ASM
FLD qword ptr [x]
FSUB ST(0), ST(0)
```
FSUBP指令也是減法指令,但不同于FSUB,它不需要第一個(gè)操作數(shù),而是將棧頂?shù)膬蓚€(gè)浮點(diǎn)數(shù)相減。將棧頂?shù)膬蓚€(gè)浮點(diǎn)數(shù)相減后,F(xiàn)SUBP指令將彈出棧頂部的浮點(diǎn)數(shù),將結(jié)果存儲(chǔ)在次棧頂?shù)母↑c(diǎn)寄存器中。語(yǔ)法如下:
```ASM
FSUBP destination
```
其中,destination可以是寄存器或內(nèi)存地址。例如,要將浮點(diǎn)寄存器ST(0)中的值減去雙精度浮點(diǎn)數(shù) x ,并將結(jié)果存儲(chǔ)到內(nèi)存地址 z 中,則可以使用以下指令:
```ASM
FLD qword ptr [x]
FSUBP ST(1), ST(0)
FSTP qword ptr [z]
```
FISUB指令用于將有符號(hào)整數(shù)從浮點(diǎn)數(shù)中減去。它從存儲(chǔ)有符號(hào)整數(shù)的內(nèi)存地址或寄存器中裝載整數(shù)值,并將其作為源操作數(shù),從浮點(diǎn)寄存器中的另一個(gè)浮點(diǎn)數(shù)中減去。FISUB指令的語(yǔ)法類(lèi)似于FSUB指令,如下所示:
```ASM
FISUB destination, source
```
其中, destination 表示目的寄存器或內(nèi)存地址,source 表示有符號(hào)整數(shù)存儲(chǔ)的寄存器或內(nèi)存地址。例如,要將浮點(diǎn)寄存器 ST(0) 中的值減去 16位來(lái)源于內(nèi)存中的有符號(hào)整數(shù) y ,并將結(jié)果存儲(chǔ)回 ST(0) 中,則可以使用以下指令:
```ASM
FILD word ptr [y]
FSUB ST(0), ST(0)
```
接著讀者看如下案例,這里準(zhǔn)備了一個(gè)簡(jiǎn)單的匯編案例,通過(guò)四種不同的方式實(shí)現(xiàn)了匯編語(yǔ)言中的浮點(diǎn)數(shù)減法運(yùn)算,讀者可自行編譯并理解其中的實(shí)現(xiàn)原理;
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? Array? ? ? QWORD 10.0,20.0,30.0,40.0,50.0
? IntQWORD? ?QWORD 20
? Result QWORD ?
??
? szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0?
.code
? main PROC
? ? finit
? ? fld qword ptr ds:[Array]
? ? fld qword ptr ds:[Array + 8]
? ? fld qword ptr ds:[Array + 16]
? ? fld qword ptr ds:[Array + 24]
? ? fld qword ptr ds:[Array + 32]
? ??
? ? ; 第一種:無(wú)操作數(shù)減法
? ? ;fsub
? ? ;fsubp? ? ? ? ? ? ? ? ? ? ? ? ?; st(0) = st(0) - st(1)
? ??
? ? ; 第二種:兩個(gè)浮點(diǎn)數(shù)寄存器相減
? ? fsub st(0),st(1)? ? ? ? ? ? ? ?; st(0) = st(0) - st(1)
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第三種:寄存器與內(nèi)存相減
? ? fsub qword ptr ds:[Array]? ? ? ; st(0) = st(0) - Array
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第四種:與整數(shù)相減
? ? fisub dword ptr ds:[IntQWORD]? ; st(0) = st(0) - IntQWORD
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ? int 3
? main ENDP
END main
```
### 9.5 FMUL/FMULP/FIMUL
針對(duì)浮點(diǎn)數(shù)乘法指令有三種`FMUL/FMULP/FIMUL`第一個(gè)指令用于將堆棧上的浮點(diǎn)數(shù)相乘返回值放入到堆棧上,第二個(gè)指令則是相乘后將結(jié)果從堆棧中彈出,第三個(gè)指令則是將浮點(diǎn)數(shù)相乘并將結(jié)果存儲(chǔ)回堆棧中,針對(duì)浮點(diǎn)數(shù)乘法指令總結(jié)如下:
?- FMUL指令:將堆棧上的兩個(gè)浮點(diǎn)數(shù)相乘,并將結(jié)果存儲(chǔ)回堆棧中。它可以只在`ST0`和`ST1`之間執(zhí)行乘法操作。例如,執(zhí)行FMUL ST1, ST0將ST0和ST1中的兩個(gè)數(shù)相乘,并將結(jié)果存儲(chǔ)回`ST1`中。 FMUL指令使用棧操作數(shù)。
?- FMULP指令:將堆棧上的兩個(gè)浮點(diǎn)數(shù)相乘,但是不同于FMUL,它會(huì)從棧中彈出一個(gè)浮點(diǎn)數(shù)。例如,執(zhí)行FMULP ST1, ST0將ST0和ST1中的兩個(gè)數(shù)相乘,并將結(jié)果存儲(chǔ)回`ST1`中,然后將`ST0`從堆棧中彈出。 FMULP指令使用棧操作數(shù)。
?- FIMUL指令:將堆棧上的兩個(gè)浮點(diǎn)數(shù)(或整數(shù))相乘,并將結(jié)果存儲(chǔ)回堆棧中。它只在`ST0`和`ST1`之間執(zhí)行乘法操作,但是當(dāng)它們的值為整數(shù)時(shí),使用的密度為`16位`(計(jì)算2個(gè)字)。例如,執(zhí)行`FIMULWORD PTR [eax]`通常用于使用16位整數(shù)執(zhí)行浮點(diǎn)數(shù)乘法。 FIMUL指令使用棧操作數(shù)。
FMUL指令用于將浮點(diǎn)寄存器或內(nèi)存中的浮點(diǎn)數(shù)乘以另一個(gè)浮點(diǎn)數(shù),并將結(jié)果存儲(chǔ)回寄存器中。FMUL指令支持多種操作數(shù)類(lèi)型,包括寄存器、內(nèi)存、以及立即值等。例如,將浮點(diǎn)寄存器ST(0)中的值乘以雙精度浮點(diǎn)數(shù)x,并將結(jié)果存儲(chǔ)回ST(0),可以使用以下指令:
```ASM
FLD qword ptr [x]
FMUL ST(0), ST(0)
```
FMULP指令也是乘法指令,它將棧頂部的兩個(gè)浮點(diǎn)數(shù)相乘,并將結(jié)果存儲(chǔ)在次棧頂?shù)母↑c(diǎn)寄存器中。與`FSUBP`類(lèi)似,它不需要第一個(gè)操作數(shù)。例如,將棧頂?shù)膬蓚€(gè)單精度浮點(diǎn)數(shù)相乘,并將結(jié)果存儲(chǔ)到內(nèi)存z中,可以使用以下指令:
```ASM
FMULP ST(1), ST(0)
FSTP dword ptr [z]
```
FIMUL指令用于將有符號(hào)整數(shù)乘以浮點(diǎn)寄存器中的另一個(gè)浮點(diǎn)數(shù)。與`FISUB`類(lèi)似,它加載一個(gè)有符號(hào)整數(shù)并將其作為源操作數(shù)組合浮點(diǎn)寄存器中另一個(gè)浮點(diǎn)數(shù)進(jìn)行乘法運(yùn)算。例如,將浮點(diǎn)寄存器`ST(0)`中的值乘以`16`位有符號(hào)整數(shù) y,并將結(jié)果存儲(chǔ)回ST(0),可以使用以下指令:
```ASM
FILD word ptr [y]
FMUL ST(0), ST(0)
```
接下來(lái)我們通過(guò)一個(gè)案例,并使用三種不同的浮點(diǎn)數(shù)乘法指令,分別演示四種不同的乘法計(jì)算方式,讀者可自行編譯學(xué)習(xí);
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? Array? ? ? QWORD 10.0,20.0,30.0,40.0,50.0
? IntQWORD? ?QWORD 20
? Result? ? ?QWORD ?
??
? szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0?
.code
InitFLD PROC
? finit
? fld qword ptr ds:[Array]
? fld qword ptr ds:[Array + 8]
? fld qword ptr ds:[Array + 16]
? fld qword ptr ds:[Array + 24]
? fld qword ptr ds:[Array + 32]
? ret
InitFLD endp
? main PROC
? ? invoke InitFLD
? ? ; 第一種:無(wú)操作數(shù)乘法與除法
? ? fmul
? ? fmulp? ? ? ? ? ? ? ; st(0) = st(0) * st(1)
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第二種:兩個(gè)浮點(diǎn)數(shù)寄存器之間的乘法
? ? invoke InitFLD
? ? fmul st(0),st(4)? ? ; st(0) = st(0) * st(4)
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ? ; 第三種:寄存器與內(nèi)存之間的乘法與除法
? ? invoke InitFLD
? ? fmul qword ptr ds:[Array + 8]? ? ?; st(0) = st(0) * [Array + 8]
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第四種:與整數(shù)之間的乘法
? ? invoke InitFLD
? ? fimul dword ptr ds:[IntQWORD]? ? ?; st(0) = st(0) * IntQWORD
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? int 3
? main ENDP
END main
```
### 9.6 FDIV/FDIVP/FIDIV
對(duì)于浮點(diǎn)數(shù)除法運(yùn)算其調(diào)用原理與乘法運(yùn)算完全一致,對(duì)于浮點(diǎn)數(shù)除指令同樣包含有`FDIV/FDIVP/FIDIV`這三種類(lèi)型,如下則是三種類(lèi)型的說(shuō)明:
?- FDIV指令:將堆棧上的`ST1`浮點(diǎn)數(shù)除以`ST0`浮點(diǎn)數(shù),并將結(jié)果存儲(chǔ)回`ST1`中。 FDIV指令使用棧操作數(shù)。
?- FDIVP指令:將堆棧上的`ST1`浮點(diǎn)數(shù)除以`ST0`浮點(diǎn)數(shù),不同于FDIV,它還將`ST0`從堆棧中彈出。例如,執(zhí)行FDIVP ST1, ST0將ST1除以ST0,將結(jié)果存儲(chǔ)回ST1中,然后將`ST0`從堆棧中彈出。 FDIVP指令使用棧操作數(shù)。
?- FIDIV指令:將堆棧上的浮點(diǎn)數(shù)(或整數(shù))`ST0`被`ST1`浮點(diǎn)數(shù)乘除,并將結(jié)果存儲(chǔ)回堆棧中。 FIDIV指令使用棧操作數(shù)。
FDIV指令用于將浮點(diǎn)寄存器或內(nèi)存中的浮點(diǎn)數(shù)除以另一個(gè)浮點(diǎn)數(shù),并將結(jié)果存儲(chǔ)回寄存器中。FDIV指令也支持多種操作數(shù)類(lèi)型。例如,將浮點(diǎn)寄存器ST(0)中的值除以雙精度浮點(diǎn)數(shù) x,并將結(jié)果存儲(chǔ)回ST(0),可以使用以下指令:
```ASM
FLD qword ptr [x]
FDIV ST(0), ST(0)
```
FDIVP指令也是除法指令,它將棧頂兩個(gè)浮點(diǎn)數(shù)相除,將次棧頂?shù)母↑c(diǎn)數(shù)彈出并將結(jié)果存儲(chǔ)回次棧頂中。與`FSUBP`和`FMULP`指令類(lèi)似,它不需要第一個(gè)操作數(shù)。例如,將棧頂?shù)膬蓚€(gè)單精度浮點(diǎn)數(shù)相除,并將結(jié)果存儲(chǔ)到內(nèi)存`z`中,可以使用以下指令:
```ASM
FDIVP ST(1), ST(0)
FSTP dword ptr [z]
```
FIDIV 指令用于將浮點(diǎn)寄存器中的另一個(gè)浮點(diǎn)數(shù)除以有符號(hào)整數(shù),與`FIMUL`相似,它加載有符號(hào)整數(shù)并將其作為除數(shù)進(jìn)行浮點(diǎn)數(shù)除法運(yùn)算。例如,將浮點(diǎn)寄存器`ST(0)`中的值除以`16`位有符號(hào)整數(shù) y ,并將結(jié)果存儲(chǔ)回`ST(0)`,可以使用以下指令:
```ASM
FILD word ptr [y]
FDIV ST(0), ST(0)
```
接下來(lái)我們通過(guò)一個(gè)案例,并使用三種不同的浮點(diǎn)數(shù)乘法指令,分別演示四種不同的除法計(jì)算方式,讀者可自行編譯學(xué)習(xí);
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? Array? ? ? QWORD 10.0,20.0,30.0,40.0,50.0
? IntQWORD? ?QWORD 20
? Result? ? ?QWORD ?
??
? szFmt BYTE 'ST寄存器: %f ',0dh,0ah,0?
.code
InitFLD PROC
? finit
? fld qword ptr ds:[Array]
? fld qword ptr ds:[Array + 8]
? fld qword ptr ds:[Array + 16]
? fld qword ptr ds:[Array + 24]
? fld qword ptr ds:[Array + 32]
? ret
InitFLD endp
? main PROC
? ? invoke InitFLD
? ? ; 第一種:無(wú)操作數(shù)除法
? ? fdiv
? ? fdivp? ? ? ? ? ? ? ; st(0) = st(0) / st(1)
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第二種:兩個(gè)浮點(diǎn)數(shù)寄存器之間的除法
? ? invoke InitFLD
? ??
? ? fdiv st(0),st(2)? ? ; st(0) = st(0) / st(2)
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ? ; 第三種:寄存器與內(nèi)存之間的乘法與除法
? ? invoke InitFLD
? ??
? ? fdiv qword ptr ds:[Array + 16]? ? ; st(0) = st(0) / [Array + 16]
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ??
? ? ; 第四種:與整數(shù)之間的乘法與除法
? ? invoke InitFLD
? ??
? ? fidiv dword ptr ds:[IntQWORD]? ? ?; st(0) = st(0) / IntQWORD
? ? fst qword ptr ds:[Result]
? ? invoke crt_printf,addr szFmt,qword ptr ds:[Result]
? ? int 3
? main ENDP
END main
```
### 9.7 FCOM/FCOMP/FCOMPP
浮點(diǎn)數(shù)比較指令包括`FCOM/FCOMP/FCOMPP`這三個(gè)指令都是比較`ST(0)`和源操作數(shù),源操作數(shù)可以是內(nèi)存操作數(shù)或`FPU`寄存器,`FCOM`和`FCOMP`格式基本一致,唯一區(qū)別在于`FCOMP`在執(zhí)行對(duì)比后還要從堆棧中彈出元素,而`FCOMP`和`FCOMPP`也基本一致,最后都是要從堆棧中彈出元素。
FCOM 指令用于比較浮點(diǎn)數(shù)寄存器`ST(0)`和源操作數(shù)中的浮點(diǎn)數(shù),并設(shè)置狀態(tài)字以指示兩個(gè)數(shù)的關(guān)系。源操作數(shù)可以是內(nèi)存操作數(shù)或者`FPU`寄存器。例如,比較浮點(diǎn)數(shù)寄存器`ST(0)`和內(nèi)存中的雙精度浮點(diǎn)數(shù)x,可以使用以下指令:
```ASM
FLD qword ptr [x]
FCOM ST(0)
```
FCOMP指令與`FCOM`指令類(lèi)似,只是在執(zhí)行比較后,除了設(shè)置狀態(tài)字以外,還會(huì)將棧頂元素彈出。例如,比較浮點(diǎn)數(shù)寄存器`ST(0)`和浮點(diǎn)數(shù)寄存器`ST(1)`,并將棧頂元素彈出,可以使用以下指令:
```ASM
FCOM ST(1)
FCOMP
```
FCOMPP指令也是用于比較兩個(gè)浮點(diǎn)數(shù)寄存器`ST(0)`和`ST(1)`的大小,并將棧頂?shù)膬蓚€(gè)元素彈出。與`FCOMP`指令類(lèi)似,但是可以比較兩個(gè)棧頂?shù)臄?shù)值。例如,比較浮點(diǎn)數(shù)寄存器ST(0)和ST(1),并將棧頂?shù)膬蓚€(gè)元素彈出,可以使用以下指令:
```ASM
FCOMPP
```
比較指令的重點(diǎn)就是比較條件碼的狀態(tài),F(xiàn)PU中包括三個(gè)條件狀態(tài),分別是`C3(零標(biāo)志),C2(奇偶標(biāo)志),C0(進(jìn)位標(biāo)志)`,我們可以使用`FNSTSW`指令將這些狀態(tài)字送入`AX`寄存器中,然后通過(guò)`SAHF`指令把`AH`賦值到`EFLAGS`標(biāo)志中,一旦標(biāo)志狀態(tài)被送入`EFLAGS`寄存器,那么就可以使用標(biāo)準(zhǔn)的標(biāo)志位對(duì)跳轉(zhuǎn)指令進(jìn)行影響,例如以下C語(yǔ)言代碼實(shí)現(xiàn)。
```c
double x = 1.2; double y = 3.0; int n = 0;
if(x < y)
{
? n=1;
}
```
當(dāng)此段代碼使用匯編語(yǔ)言實(shí)現(xiàn)時(shí),讀者可寫(xiě)出如下所示的對(duì)等代碼,其中當(dāng)調(diào)用`fcomp`時(shí)自動(dòng)完成比較并通過(guò)`fnstsw ax`的方式將狀態(tài)值送入到AX寄存器中,然后再調(diào)用`sahf`將狀態(tài)值送入到`EFLAGS`寄存器組,此時(shí),我們就可以使用通用的跳轉(zhuǎn)指令實(shí)現(xiàn)跳轉(zhuǎn)了。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? x REAL8 1.2
? y REAL8 3.0
? n DWORD 0
.code
? main PROC
? ? fld x? ? ? ? ; st(0) = x
? ? fcomp y? ? ? ; cmp x,y ; pop x
? ? fnstsw ax? ? ; 取出狀態(tài)值送入AX
? ? sahf? ? ? ? ?; 將狀態(tài)字送入EFLAGS
? ? jnb L1? ? ? ?; x < y 小于
? ? mov n,1? ? ? ; 滿足則將n置1
? L1: xor eax,eax? ; 否則清空寄存器
? ? int 3
? main ENDP
END main
```
對(duì)于上述中所示的案例來(lái)說(shuō),由于浮點(diǎn)數(shù)運(yùn)算比整數(shù)運(yùn)算在開(kāi)銷(xiāo)上會(huì)更大一些,因此Intel新版處理器新增加了`FCOMI`指令,專(zhuān)門(mén)用于比較兩個(gè)浮點(diǎn)數(shù)的值,并自動(dòng)設(shè)置零標(biāo)志,基偶標(biāo)志,和進(jìn)位標(biāo)志,唯一的缺點(diǎn)是其不支持內(nèi)存操作數(shù),但當(dāng)讀者需要使用是也是可以使用的,這段案例如下所示;
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? x REAL8 1.2
? y REAL8 3.0
? n DWORD 0
.code
? main PROC
? ? fld y
? ? fld x
? ? fcomi st(0),st(1)
? ? jnb L1? ? ? ? ? ? ; st(0) not st(1) ?
? ? mov n,1
? ??
? L1: xor eax,eax
? ? int 3
? main ENDP
END main
```
對(duì)于浮點(diǎn)數(shù)的比較來(lái)說(shuō),例如比較X與Y是否相等,如果比較`X==y?`則可能會(huì)出現(xiàn)近似值的情況,導(dǎo)致無(wú)法計(jì)算出正確結(jié)果,正確的做法是取其差值的絕對(duì)值,并和用戶自定義的小的正數(shù)相比較,小的正整數(shù)作為兩個(gè)值相等時(shí)其差值的臨界值。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
? epsilon REAL8 1.0E-12
? var2? ? REAL8 0.0
? var3? ? REAL8 1.001E-13
.code
? main PROC
? ? fld epsilon
? ? fld var2
? ? fsub var3
? ? fabs
? ? fcomi st(0),st(1) ; cmp epsilon,var2
? ? ja skip
? ? xor ebx,ebx? ? ? ?; 相等則清空ebx
? skip:
? ? int 3? ? ? ? ? ? ?; 不相等則結(jié)束
? main ENDP
END main
```
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/d9e9813d.html
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請(qǐng)注明出處!