汇编语言FCOM指令:比较浮点数值

浮点数不能使用 CMP 指令进行比较,因为后者是通过整数减法来执行比较的。取而代之,必须使用 FCOM 指令。

执行 FCOM 指令后,还需要采取特殊步骤,然后再使用逻辑 IF 语句中的条件跳转指令(JA、JB、JE 等)。由于所有的浮点数都为隐含的有符号数,因此,FCOM 执行的是有符号比较。

FCOM、FCOMP、FCOMPP

FCOM(比较浮点数)指令将其源操作数与 ST(0) 进行比较。源操作数可以为内存操作数或 FPU 寄存器。其语法如下表所示:

指令 说明
FCOM 比较 ST(0) 与 ST(1)
FCOM m32fp 比较 ST(0) 与 m32fp
FCOM m64fp 比较 ST(0) 与 m64fp
FCOM ST(i) 比较 ST(0) 与 ST(i)

FCOMP 指令的操作数类型和执行的操作与 FCOM 指令相同,但是它要将 ST(0) 弹岀堆栈。FCOMPP 指令与 FCOMP 相同,但是它有两次出栈操作。

条件码

FPU 条件码标识有 3 个,C3、C2 和 C0,用以说明浮点数比较的结果,如下表所示。由于 C3、C2 和 C0 的功能分别与零标志位 (ZF)、奇偶标志位 (PF) 和进位标志位 (CF) 相同,因此表中列标题给出了与之等价的 CPU 状态标识。

条件 C3(零标志位) C2(奇偶标志位) C0(进位标志位) 使用的条件跳转指令
ST(0) > SPC 0 0 0 JA.JNBE
ST(0) < SPC 0 0 1 JB.JNAE
ST(0) = SPC 1 0 0 JE.JZ
无序 1 1 1 (无)

提示:如果出现无效算术运算操作数异常(无效操作数),且该异常被屏蔽,则 C3、C2 和 C0 按照标记为“无序”的行来设置。

在比较了两个数值并设置了 FPU 条件码之后,遇到的主要挑战就是怎样根据条件分支到相应标号。这包括了两个步骤:
  • 用 FNSTSW 指令把 FPU 状态字送入 AX。
  • 用 SAHF 指令把 AH 复制到 EFLAGS 寄存器。

条件码送入 EFLAGS 之后,就可以根据 ZF、PF 和 CF 进行条件跳转。上表列出了每种标志位组合所对应的条件跳转。根据该表还可以推出其他跳转:如果 CF=0,则可以使用 JAE 指令引发控制转移;如果 CF=1 或 ZF=1,则可使用 JBE 指令引发控制转移;如果 ZF=0,则可使用 JNE 指令。

【示例】现有如下 C++ 代码段:

double X = 1.2;
double Y = 3.0;
int N = 0;
if( X < Y )
N = 1;

与之等效的汇编语言代码如下:

.data
X REAL8 1.2
Y REAL8 3.0
N DWORD 0
.code
if( X < Y )
    ; N = 1
    fid X              ; ST(0) = X
    fcomp Y        ;比较 ST (0)和 Y
    fnstsw ax      ;状态字送入AX
    sahf              ;AH 复制至!) EFLAGS
    jnb L1           ;X不小于Y?跳过
    mov Nz1       ; N = 1
L1:

P6 处理器的改进

对上面的例子需要说明一点的是浮点数比较的运行时开销大于整数比较。考虑到这一点,Intel P6 系列引入了 FCOMI 指令。该指令比较浮点数值,并直接设置 ZF、PF 和 CF。P6 系列以 Pentium Pro 和 Pentium II 处理器为起点。) FCOMI 的语法如下:

FCOMI 指令代替了之前代码段中的三条指令,但是增加了一条 FLD 指令。FCOMI 指令不使用内存操作数。

相等比较

几乎所有的编程入门教材都会警告读者不要进行浮点数相等的比较,其原因是在计算
过程中出现的舍入误差。现在通过计算表达式 (sqrt(2.0)*sqrt(2.0)) -2.0 来对这个问题进行说明。从数学上看,这个表达式应 该等于0,但计算结果却相差甚远(约等于 4.4408921E-016)。 使用如下数据,下表列出了每一步计算后FPU堆栈的情况:

vail REAL8 2.0


指令 FPU堆栈
fidvall  ST(0) : +2.0000000E+000
fsqrt ST(0) : +1.4142135E+000
fmul ST(0), ST(0)    ST(0) : +2.0000000E+000
fsub vail ST(0) : +4.4408921E-016

比较两个浮点数 n 和 y 的正确方法是取它们差值的绝对值|x-y|,再将其与用户定义的误差值 epsilon 进行比较。汇编语言代码如下,其中,epsilon 为两数差值允许的最大值,不 大于该值则认为这两个浮点数相等:

.data
epsilon REAL8 1.0E-12
val2 REAL8 0.0                      ;比较的数值
val3 REAL8 1.01E —13          ;认为等于^&丄2
.code
;如果 (val2 == val3 ),显示"Values are equal".
fid epsilon
fid val2
fsu val3
fabs
fcomi ST(0)ZST(1)
ja skip
mWrite <"Values are equal",Odh,0ah>
skip:

下表跟踪程序执行过程,显示了前四条指令执行后的堆栈情况。

指令  FPU堆栈 指令 FPU堆栈
fid epsilon ST(0): +1.0000000E-012   ST(1): +1.0000000E-012
fid val2 ST(0): +0.0000000E+000 fabs ST(0): +1.0010000E-013
  ST(1): +1.0000000E-012   ST(1): +1.0000000E-012
fsub val3 ST(0): -1.0010000E-013 fcomi ST(0), ST(1) ST(0)<ST(1), so CF=1, ZF=0

如果将 val3 重新定义为大于 epsilon,它就不会等于 val2:

val3 REAL8  1.001E-12    ;不相等