将 FASM 中的 80 位浮点数与给定精度进行比较

Comparing 80 bit floats in FASM with given accuracy

我正在编写一个程序,在循环中使用 Nilakantha 系列计算 Pi,精度至少为 0.05%。这个循环的退出条件应该是当当前计算值res和之前计算值prev符合|res - prev|时。 <= 0.0005。我已经阅读了 FASM 中的一些浮点比较,但仍然不完全理解它是如何工作的。目前程序只是无限执行,永远不会退出循环。在调试期间,我看到浮点数经常变成 1.#IND00,这应该是一个 NaN。如何写出准确的比较?

format PE console
entry start

include 'win32a.inc'


section '.code' code readable executable
; 3 + 4/(2*3*4) - 4 / (4*5*6) + 4/(6*7*8) - ...
start:
FINIT
piLoop:

; calculating denominator of fraction that will be added: x1*x2*x3
FLD [denominator]
FMUL [zero]
FADD [x1]
FMUL [x2]
FMUL [x3]
FSTP [denominator]

; changing denominator product values for next loop: x1 +=2, x2 += 2, x3 += 2
FLD [x1]
FADD [stepValue]
FSTP [x1]
FLD [x2]
FADD [stepValue]
FSTP [x2]
FLD [x3]
FADD [stepValue]
FSTP [x3]

;calculating numerator: multiplying numerator by -1
FLD [numerator]
FMUL [sign]
FSTP [numerator]

; calculating fraction: +-4 / (x1 * x2 * x3)
FLD [numerator]
FDIV [denominator]
FSTP [fraction]

; adding calculated fraction to our answer
FLD [res]
FADD [fraction]
FSTP [res]

; the comparison part, incorrect?
FLD [res]
FSUB [prev]
FABS
FCOM [accuracy]
FSTSW AX
SAHF

add [i], 1


; prev = res
FLD [res]
FSTP [prev]
jb endMet
jmp piLoop
endMet:

invoke printf, steps_string, [i]

invoke getch
invoke ExitProcess, 0

section '.data' data readable writable
steps_string db "Calculation completed. The Nilakantha Series took %d steps.",10,0
pi_string db "accurate pi = %lf, calculated pi = %lf", 10, 0


res dq 3.0
x1 dq 2.0
x2 dq 3.0
x3 dq 4.0
stepValue dq 2.0
fraction dq 0.0
numerator dq -4.0
denominator dq 0.0
sign dq -1.0
zero dq 0.0
N dd 20
i dd 0
accuracy dq 0.0005
calc dq ?
prev dq 3.0

section '.idata' import data readable
library kernel, 'kernel32.dll',\
        msvcrt, 'msvcrt.dll',\
        user32,'USER32.DLL'

include 'api\user32.inc'
include 'api\kernel32.inc'
import kernel,\
       ExitProcess, 'ExitProcess',\
       HeapCreate,'HeapCreate',\
       HeapAlloc,'HeapAlloc'
include 'api\kernel32.inc'
import msvcrt,\
       printf, 'printf',\
       sprintf, 'sprintf',\
       scanf, 'scanf',\
       getch, '_getch'  

(只是扩展我的评论,以便得到答案。)

对于背景:floating-point 比较的复杂指令序列来自早期 x86 CPUs 没有 FPU on-board 的事实;它是一个可选的独立芯片,它与 CPU 交互的能力是有限的。所以FCOM指令不能直接设置CPU的FLAGS寄存器。相反,它设置 floating-point 协处理器内部的浮点状态字。 FSTSW 指令可用于从协处理器获取状态字并将其加载到 general-purpose CPU 寄存器,然后 SAHF 将获取 AH 的适当位并将它们写入 FLAGS.

完成所有这些之后,您终于得到了 FLAGS 集来指示比较的结果,并且状态字的位被布置以便以与整数比较相同的方式设置 FLAGS:ZF 将是如果数字相等则设置,如果差异严格为负则设置 CF,依此类推。因此,您现在可以使用 jajb 等条件跳转,就像您对无符号整数进行比较一样。请注意,PF=1 意味着比较是无序的(至少一个操作数是 NaN),因此您需要先检查一下。

(PPro 添加 FCOMI which sets EFLAGS from the FP compare the same way fcom/fstsw/sahf does, avoiding the extra instructions. See also


但是,您的代码在 之间有 add [i], 1,并且像大多数 x86 算术指令一样,它根据结果设置 FLAGS。所以 你仔细检索的 FLAGS 被覆盖 ,并且 jb 几行之后是基于 add 而不是 FCOM 的结果。因此你需要重新排列它们。

例如,在SAHF之前执行add。或者在 fcomi.

之前