5.9 汇编语言:浮点数操作指令

2023-10-11 14:56:33 浏览数 (2)

汇编语言是一种面向机器的低级语言,用于编写计算机程序。汇编语言与计算机机器语言非常接近,汇编语言程序可以使用符号、助记符等来代替机器语言的二进制码,但最终会被汇编器编译成计算机可执行的机器码。

浮点运算单元是从80486处理器开始才被集成到CPU中的,该运算单元被称为FPU浮点运算模块,FPU不使用CPU中的通用寄存器,其有自己的一套寄存器,被称为浮点数寄存器栈,FPU将浮点数从内存中加载到寄存器栈中,完成计算后在回写到内存中。

FPU有8个可独立寻址的80位寄存器,分别名为R0-R7他们以堆栈的形式组织在一起,栈顶由FPU状态字中的一个名为TOP的域组成,对寄存器的引用都是相对于栈顶而言的,栈顶通常也被叫做ST(0)最后一个栈底则被记作ST(7)其使用方式与堆栈一致。

浮点数运算通常会使用一些更长的数据类型,如下就是MASM汇编器定义的常用数据类型.

代码语言:javascript复制
.data
  var1 QWORD  10.1    ; 64位整数
  var2 TBYTE  10.1    ; 80位(10字节)整数
  var3 REAL4  10.2    ; 32位(4字节)短实数
  var4 REAL8  10.8    ; 64位(8字节)长实数
  var5 REAL10 10.10   ; 80位(10字节)扩展实数

此外浮点数对于指令的命名规范也遵循一定的格式,浮点数指令总是以F开头,而指令的第二个字母则表示操作位数,例如:B表示二十进制操作数,I表示二进制整数操作,如果没有指定则默认则是针对实数的操作fld等.

9.1 FLD/FSTP

FLD 和 FSTP 是x86架构处理器中的浮点操作指令,FLD指令用于将浮点数从内存装载进浮点寄存器,或者FSTP指令从浮点寄存器存储到内存中。

FLD 指令用于从内存中读取单精度浮点数(32位)或双精度浮点数(64位),并将其存储到浮点栈中。FLD 指令的语法如下:

代码语言:javascript复制
FLD source

其中,source 可以是内存地址、寄存器或立即数。例如,要将双精度浮点数3.14159存储到浮点栈中,可以使用以下指令:

代码语言:javascript复制
movsd xmm0, [pi]      ; 将pi常量的值放入xmm0寄存器中
movsd [esp], xmm0     ; 将xmm0寄存器中的值存储到栈顶
fld qword ptr [esp]   ; 将栈顶的值从内存中装载到浮点栈中

其中,xmm0 是双精度浮点寄存器,pi 是一个双精度浮点常量的地址,esp 是堆栈指针寄存器,qword ptr标记用于指示要读取的内存单元的数据大小。

FSTP 指令用于将浮点栈顶的值弹出,并将其存储到内存中。FSTP指令的语法如下:

代码语言:javascript复制
FSTP destination

其中,destination 可以是内存地址、寄存器或立即数。例如,将浮点栈顶的值存储到内存单元 x 中,可以使用以下指令:

代码语言:javascript复制
fstp qword ptr [x]    ; 将浮点栈顶的值存储到 x 变量的内存单元中

需要注意,FSTP 指令会将浮点栈顶部的值弹出,在栈顶的值被存储到目标地址之后,浮点栈顶部的指针将自动下移。

代码语言:javascript复制
  .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
    ; 初始化浮点单元
    finit
    
    ; 依次将数据入栈
    fld qword ptr ds:[var1]
    fld qword ptr ds:[var2]
    fld qword ptr ds:[var3]
    fld qword ptr ds:[var4]
    
    ; 获取当前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

压栈指令同样支持变址寻址的方式,如下代码案例中我们可以通过循环将一个数组压入浮点数寄存器,其中使用FLD指令时压入一个浮点实数,而FILD则是将实数转换为双精度浮点数后压入堆栈。

代码语言:javascript复制
  .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
  
    ; 初始化浮点单元
    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]   ; 压入浮点实数
    fild qword ptr ds:[Array   eax * 8]  ; 压入双精度浮点数

    jmp L2
  lop_end:
    int 3
  main ENDP
END main

9.2 FCHS/FABS

FCHS 指令是x86架构处理器中的浮点数操作指令,该指令可用于把ST(0)中值的符号变反,FABS 指令用于将浮点数的值取绝对值。这两条指令的操作对象是浮点寄存器,而不是浮点栈。因此,它并未涉及堆栈操作或操作数的传递。在使用 FCHS 和 FABS 指令时,需要使用浮点操作指令前缀 F 来标识它们是浮点数操作指令。

代码语言:javascript复制
  .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]

    ; 对ST(0)数据取反 (不影响浮点堆栈)
    fchs                                               ; 对ST(0)取反
    fchs                                               ; 再次取反
    fst qword ptr ds:[Result]                          ; 取ST(0)赋值到Result
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    
    ; 循环将数组取反后回写如Array中
    mov ecx,5
  S1:
    fchs
    fstp qword ptr ds:[Array   ecx * 8]
    loop S1
    
    ; 读入Array中的数据到ST寄存器
    mov ecx,5
  S2:
    fld qword ptr ds:[Array   ecx * 8]
    loop S2
    
    ; 通过FABS取绝对值,并反写会Array中
    mov ecx,5
  S3:
    fabs                                  ; 取ST(0)的绝对值
    fstp qword ptr ds:[Array   ecx * 8]   ; 反写
    loop S3
    
    int 3
  main ENDP
END main

9.3 FADD/FADDP/FIADD

浮点数加法系列指令,该系列可分为FADD/FADDP/FIADD,这些指令分别针对不同的场景使用,此外还会区分无操作数模式,寄存器操作数,内存操作数,整数相加等。这些指令用于不同的场景下进行操作,如下所述:

FADD 指令用于将两个浮点数相加,并将结果存储到浮点寄存器中。FADD指令支持多种操作数类型,包括无操作数模式、寄存器操作数和内存操作数等。例如,将一个双精度浮点数和一个32位整数相加,可以使用以下指令:

代码语言:javascript复制
fld qword ptr [x]    ; 将双精度浮点数x装载到栈顶
fiadd dword ptr [y]  ; 将32位整数y装载到浮点寄存器中,并与栈顶的浮点数相加
fstp qword ptr [z]   ; 将浮点栈顶的值存储到双精度浮点数z中

FADDP 指令也是用于将两个浮点数相加,但是会将结果弹出并存储到目标寄存器或内存中。FADDP指令与FADD指令最大的区别在于它弹出了浮点栈顶的值。例如,将两个单精度浮点数相加并将结果存储到内存中,可以使用以下指令:

代码语言:javascript复制
fld dword ptr [x]    ; 将单精度浮点数x1装载到栈顶
fadd dword ptr [y]   ; 将单精度浮点数x2装载到栈顶,并与栈顶的数相加
fstp dword ptr [z]   ; 将浮点栈顶的值存储到单精度浮点数z中,同时弹出栈顶

FIADD 指令用于将一个整数加到浮点寄存器的值中。与FADD指令不同,其支持的数据类型只有整数类型,而没有浮点数类型。使用FIADD指令时,要将操作数用一个寄存器或内存地址表示。例如,将一个16位有符号整数加到浮点数中,可以使用以下指令:

代码语言:javascript复制
fild word ptr [x]   ; 将16位有符号整数x装载到浮点寄存器中
fadd dword ptr [y]  ; 将32位浮点数y装载到栈顶,并与浮点寄存器中的整数相加
fstp dword ptr [z]  ; 将浮点栈顶的值存储到双精度浮点数z中

如下汇编代码将分别总结四种不同的浮点数计算方式,读者可自行根据提示信息理解这其中的含义。

  • 第一种:无操作数模式,执行FADD时,ST(0)寄存器和ST(1)寄存器相加后,结果临时存储在ST(1)中,然后将ST(0)弹出堆栈,最终结果就会存储在栈顶部,使用FST指令即可取出来。
  • 第二种:则是两个浮点寄存器相加,最后的结果会存储在源操作数ST(0)中。
  • 第三种:则是内存操作数,就是ST寄存器与内存相加。
  • 第四种:是与整数相加,默认会将整数扩展为双精度,然后在于ST(0)相加。
代码语言:javascript复制
  .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]
    
    ; 第一种:无操作数 fadd = faddp
    ;fadd
    ;faddp
    
    ; 第二种:两个浮点寄存器相加
    fadd st(0),st(1)          ; st(0) = st(0)   st(1)
    fst qword ptr ds:[Result] ; 取出结果
    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] ; 取出结果
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    
    ; 第三种:寄存器与内存相加
    fadd qword ptr ds:[Array] ; st(0) = st(0)   Array
    fst qword ptr ds:[Result] ; 取出结果
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    
    fadd real8 ptr ds:[Array   8]
    fst qword ptr ds:[Result] ; 取出结果
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    
    ; 第四种:与整数相加
    fiadd dword ptr ds:[IntA]
    fst qword ptr ds:[Result] ; 取出结果
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    int 3
  main ENDP
END main

9.4 FSUB/FSUBP/FISUB

x86架构处理器的浮点数减法指令有FSUB/FSUBP/FISUB该系列指令从目的操作数中减去原操作数,把差存储在目的操作数中,目的操作数必须是ST寄存器,源操作数可以是寄存器或内存,运算的过程与加法指令完全一致。

FSUB指令从浮点数寄存器或内存中减去一个浮点数,并将结果存储到浮点寄存器中。语法如下:

代码语言:javascript复制
FSUB destination, source

其中, destination 表示目的寄存器或内存地址,source 表示源寄存器或内存地址。例如,要将浮点寄存器ST(0)中的值减去双精度浮点数 x ,并将结果存储回ST(0),则可以使用以下指令:

代码语言:javascript复制
FLD qword ptr [x]
FSUB ST(0), ST(0)

FSUBP指令也是减法指令,但不同于FSUB,它不需要第一个操作数,而是将栈顶的两个浮点数相减。将栈顶的两个浮点数相减后,FSUBP指令将弹出栈顶部的浮点数,将结果存储在次栈顶的浮点寄存器中。语法如下:

代码语言:javascript复制
FSUBP destination

其中,destination可以是寄存器或内存地址。例如,要将浮点寄存器ST(0)中的值减去双精度浮点数 x ,并将结果存储到内存地址 z 中,则可以使用以下指令:

代码语言:javascript复制
FLD qword ptr [x]
FSUBP ST(1), ST(0)
FSTP qword ptr [z]

FISUB指令用于将有符号整数从浮点数中减去。它从存储有符号整数的内存地址或寄存器中装载整数值,并将其作为源操作数,从浮点寄存器中的另一个浮点数中减去。FISUB指令的语法类似于FSUB指令,如下所示:

代码语言:javascript复制
FISUB destination, source

其中, destination 表示目的寄存器或内存地址,source 表示有符号整数存储的寄存器或内存地址。例如,要将浮点寄存器 ST(0) 中的值减去 16位来源于内存中的有符号整数 y ,并将结果存储回 ST(0) 中,则可以使用以下指令:

代码语言:javascript复制
FILD word ptr [y]
FSUB ST(0), ST(0)

接着读者看如下案例,这里准备了一个简单的汇编案例,通过四种不同的方式实现了汇编语言中的浮点数减法运算,读者可自行编译并理解其中的实现原理;

代码语言:javascript复制
  .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]
    
    ; 第一种:无操作数减法
    ;fsub
    ;fsubp                         ; st(0) = st(0) - st(1)
    
    ; 第二种:两个浮点数寄存器相减
    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]
    
    ; 第三种:寄存器与内存相减
    fsub qword ptr ds:[Array]      ; st(0) = st(0) - Array
    fst qword ptr ds:[Result]
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    
    ; 第四种:与整数相减
    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

针对浮点数乘法指令有三种FMUL/FMULP/FIMUL第一个指令用于将堆栈上的浮点数相乘返回值放入到堆栈上,第二个指令则是相乘后将结果从堆栈中弹出,第三个指令则是将浮点数相乘并将结果存储回堆栈中,针对浮点数乘法指令总结如下:

  • FMUL指令:将堆栈上的两个浮点数相乘,并将结果存储回堆栈中。它可以只在ST0ST1之间执行乘法操作。例如,执行FMUL ST1, ST0将ST0和ST1中的两个数相乘,并将结果存储回ST1中。 FMUL指令使用栈操作数。
  • FMULP指令:将堆栈上的两个浮点数相乘,但是不同于FMUL,它会从栈中弹出一个浮点数。例如,执行FMULP ST1, ST0将ST0和ST1中的两个数相乘,并将结果存储回ST1中,然后将ST0从堆栈中弹出。 FMULP指令使用栈操作数。
  • FIMUL指令:将堆栈上的两个浮点数(或整数)相乘,并将结果存储回堆栈中。它只在ST0ST1之间执行乘法操作,但是当它们的值为整数时,使用的密度为16位(计算2个字)。例如,执行FIMULWORD PTR [eax]通常用于使用16位整数执行浮点数乘法。 FIMUL指令使用栈操作数。

FMUL指令用于将浮点寄存器或内存中的浮点数乘以另一个浮点数,并将结果存储回寄存器中。FMUL指令支持多种操作数类型,包括寄存器、内存、以及立即值等。例如,将浮点寄存器ST(0)中的值乘以双精度浮点数x,并将结果存储回ST(0),可以使用以下指令:

代码语言:javascript复制
FLD qword ptr [x]
FMUL ST(0), ST(0)

FMULP指令也是乘法指令,它将栈顶部的两个浮点数相乘,并将结果存储在次栈顶的浮点寄存器中。与FSUBP类似,它不需要第一个操作数。例如,将栈顶的两个单精度浮点数相乘,并将结果存储到内存z中,可以使用以下指令:

代码语言:javascript复制
FMULP ST(1), ST(0)
FSTP dword ptr [z]

FIMUL指令用于将有符号整数乘以浮点寄存器中的另一个浮点数。与FISUB类似,它加载一个有符号整数并将其作为源操作数组合浮点寄存器中另一个浮点数进行乘法运算。例如,将浮点寄存器ST(0)中的值乘以16位有符号整数 y,并将结果存储回ST(0),可以使用以下指令:

代码语言:javascript复制
FILD word ptr [y]
FMUL ST(0), ST(0)

接下来我们通过一个案例,并使用三种不同的浮点数乘法指令,分别演示四种不同的乘法计算方式,读者可自行编译学习;

代码语言:javascript复制
  .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
    ; 第一种:无操作数乘法与除法
    fmul
    fmulp              ; st(0) = st(0) * st(1)
    fst qword ptr ds:[Result]
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    
    ; 第二种:两个浮点数寄存器之间的乘法
    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]

    ; 第三种:寄存器与内存之间的乘法与除法
    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]
    
    ; 第四种:与整数之间的乘法
    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

对于浮点数除法运算其调用原理与乘法运算完全一致,对于浮点数除指令同样包含有FDIV/FDIVP/FIDIV这三种类型,如下则是三种类型的说明:

  • FDIV指令:将堆栈上的ST1浮点数除以ST0浮点数,并将结果存储回ST1中。 FDIV指令使用栈操作数。
  • FDIVP指令:将堆栈上的ST1浮点数除以ST0浮点数,不同于FDIV,它还将ST0从堆栈中弹出。例如,执行FDIVP ST1, ST0将ST1除以ST0,将结果存储回ST1中,然后将ST0从堆栈中弹出。 FDIVP指令使用栈操作数。
  • FIDIV指令:将堆栈上的浮点数(或整数)ST0ST1浮点数乘除,并将结果存储回堆栈中。 FIDIV指令使用栈操作数。

FDIV指令用于将浮点寄存器或内存中的浮点数除以另一个浮点数,并将结果存储回寄存器中。FDIV指令也支持多种操作数类型。例如,将浮点寄存器ST(0)中的值除以双精度浮点数 x,并将结果存储回ST(0),可以使用以下指令:

代码语言:javascript复制
FLD qword ptr [x]
FDIV ST(0), ST(0)

FDIVP指令也是除法指令,它将栈顶两个浮点数相除,将次栈顶的浮点数弹出并将结果存储回次栈顶中。与FSUBPFMULP指令类似,它不需要第一个操作数。例如,将栈顶的两个单精度浮点数相除,并将结果存储到内存z中,可以使用以下指令:

代码语言:javascript复制
FDIVP ST(1), ST(0)
FSTP dword ptr [z]

FIDIV 指令用于将浮点寄存器中的另一个浮点数除以有符号整数,与FIMUL相似,它加载有符号整数并将其作为除数进行浮点数除法运算。例如,将浮点寄存器ST(0)中的值除以16位有符号整数 y ,并将结果存储回ST(0),可以使用以下指令:

代码语言:javascript复制
FILD word ptr [y]
FDIV ST(0), ST(0)

接下来我们通过一个案例,并使用三种不同的浮点数乘法指令,分别演示四种不同的除法计算方式,读者可自行编译学习;

代码语言:javascript复制
  .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
    ; 第一种:无操作数除法
    fdiv
    fdivp              ; st(0) = st(0) / st(1)
    fst qword ptr ds:[Result]
    invoke crt_printf,addr szFmt,qword ptr ds:[Result]
    
    ; 第二种:两个浮点数寄存器之间的除法
    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]

    ; 第三种:寄存器与内存之间的乘法与除法
    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]
    
    ; 第四种:与整数之间的乘法与除法
    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

浮点数比较指令包括FCOM/FCOMP/FCOMPP这三个指令都是比较ST(0)和源操作数,源操作数可以是内存操作数或FPU寄存器,FCOMFCOMP格式基本一致,唯一区别在于FCOMP在执行对比后还要从堆栈中弹出元素,而FCOMPFCOMPP也基本一致,最后都是要从堆栈中弹出元素。

FCOM 指令用于比较浮点数寄存器ST(0)和源操作数中的浮点数,并设置状态字以指示两个数的关系。源操作数可以是内存操作数或者FPU寄存器。例如,比较浮点数寄存器ST(0)和内存中的双精度浮点数x,可以使用以下指令:

代码语言:javascript复制
FLD qword ptr [x]
FCOM ST(0)

FCOMP指令与FCOM指令类似,只是在执行比较后,除了设置状态字以外,还会将栈顶元素弹出。例如,比较浮点数寄存器ST(0)和浮点数寄存器ST(1),并将栈顶元素弹出,可以使用以下指令:

代码语言:javascript复制
FCOM ST(1)
FCOMP

FCOMPP指令也是用于比较两个浮点数寄存器ST(0)ST(1)的大小,并将栈顶的两个元素弹出。与FCOMP指令类似,但是可以比较两个栈顶的数值。例如,比较浮点数寄存器ST(0)和ST(1),并将栈顶的两个元素弹出,可以使用以下指令:

代码语言:javascript复制
FCOMPP

比较指令的重点就是比较条件码的状态,FPU中包括三个条件状态,分别是C3(零标志),C2(奇偶标志),C0(进位标志),我们可以使用FNSTSW指令将这些状态字送入AX寄存器中,然后通过SAHF指令把AH赋值到EFLAGS标志中,一旦标志状态被送入EFLAGS寄存器,那么就可以使用标准的标志位对跳转指令进行影响,例如以下C语言代码实现。

代码语言:javascript复制
double x = 1.2; double y = 3.0; int n = 0;
if(x < y)
{
  n=1;
}

当此段代码使用汇编语言实现时,读者可写出如下所示的对等代码,其中当调用fcomp时自动完成比较并通过fnstsw ax的方式将状态值送入到AX寄存器中,然后再调用sahf将状态值送入到EFLAGS寄存器组,此时,我们就可以使用通用的跳转指令实现跳转了。

代码语言:javascript复制
  .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    ; 取出状态值送入AX
    sahf         ; 将状态字送入EFLAGS
    jnb L1       ; x < y 小于
    mov n,1      ; 满足则将n置1

  L1: xor eax,eax  ; 否则清空寄存器
    int 3
  main ENDP
END main

对于上述中所示的案例来说,由于浮点数运算比整数运算在开销上会更大一些,因此Intel新版处理器新增加了FCOMI指令,专门用于比较两个浮点数的值,并自动设置零标志,基偶标志,和进位标志,唯一的缺点是其不支持内存操作数,但当读者需要使用是也是可以使用的,这段案例如下所示;

代码语言:javascript复制
  .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

对于浮点数的比较来说,例如比较X与Y是否相等,如果比较X==y?则可能会出现近似值的情况,导致无法计算出正确结果,正确的做法是取其差值的绝对值,并和用户自定义的小的正数相比较,小的正整数作为两个值相等时其差值的临界值。

代码语言:javascript复制
  .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             ; 不相等则结束
  main ENDP
END main

本文作者: 王瑞 本文链接: https://www.lyshark.com/post/d9e9813d.html 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

0 人点赞