Сравнение 80-битных чисел с плавающей запятой в FASM с заданной точностью

Я пишу программу, которая вычисляет число Пи, используя серию Нилаканта в цикле с точностью не менее 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'  

person DuckMaster    schedule 23.10.2020    source источник
comment
Идея: переписать часть сравнения, чтобы выполнить итерацию, скажем, N раз. Сообщите число пи, найденное для петель 2, 3, 4 и 5.   -  person chux - Reinstate Monica    schedule 23.10.2020
comment
Сравнение с использованием FCOM / FSTSW / SAHF кажется правильным, чтобы оставить результат сравнения во ФЛАГАХ, но тогда ожидается, что вы немедленно сделаете условный переход, чтобы фактически выполнить какое-то действие на основе результата сравнения. Вместо этого вы сначала делаете add [i], 1, что перезаписывает флаги.   -  person Nate Eldredge    schedule 23.10.2020
comment
@NateEldredge Большое спасибо! Это немедленно устранило проблему.   -  person DuckMaster    schedule 23.10.2020


Ответы (1)


(Просто расширяю свой комментарий, чтобы получить ответ.)

Для справки: сложная последовательность инструкций для сравнения с плавающей запятой возникает из-за того, что ранние процессоры x86 не имели встроенного FPU; это был необязательный отдельный чип, и его способность взаимодействовать с ЦП была ограничена. Таким образом, инструкция FCOM не могла напрямую установить регистр FLAGS процессора. Вместо этого он устанавливает слово состояния с плавающей запятой, которое было внутренним для сопроцессора с плавающей запятой. Инструкцию FSTSW можно использовать для получения слова состояния от сопроцессора и загрузки его в регистр ЦП общего назначения, а затем SAHF получит соответствующие биты AH и запишет их в FLAGS.

После всего этого вы, наконец, получаете набор ФЛАГОВ для обозначения результата сравнения, а биты слова состояния раскладываются так, чтобы устанавливать ФЛАГИ так же, как и для целочисленного сравнения: ZF будет установлен, если числа были равно, CF, если разница была строго отрицательной, и так далее. Таким образом, теперь вы можете использовать условные переходы, такие как ja, jb и т. д., точно так же, как при сравнении целых чисел без знака. Обратите внимание, что PF=1 означает, что сравнение было неупорядоченным (по крайней мере, один операнд был NaN), поэтому вам нужно сначала проверить это.

(PPro добавил FCOMI, который устанавливает EFLAGS из FP таким же образом fcom/fstsw/sahf делает, избегая дополнительных инструкций. -instead-of-using-signed-co">Почему x86 FP сравнивает набор CF как целые числа без знака, а не использует условия со знаком?)


Однако в вашем коде есть add [i], 1 между ними, и, как и в большинстве арифметических инструкций x86, он устанавливает ФЛАГИ на основе результата. Таким образом, ваши тщательно извлеченные ФЛАГИ перезаписываются, а jb парой строк ниже основано на результате add вместо FCOM. Таким образом, вам нужно переставить их.

Например, сделайте add перед SAHF. Или до fcomi.

person Nate Eldredge    schedule 23.10.2020