Продолжайте получать NaN после выполнения некоторых вычислений в сборке

Я работаю над программой, которая находит корни квадратного уравнения. Мне удалось получить первый рут в подпрограмме root1. Однако, когда я пытаюсь найти второй корень в root2, часть /2a квадратичной формулы продолжает давать NaN.

Вот код:

INCLUDE Irvine32.inc    
INCLUDE macros.inc

.data                                                   
a real8 ?
b real8 ?
cc real8 ?

a2 real8 ?
b2 real8 ?
cc2 real8 ?

two real8 2.0
four real8 4.0

two2 real8 2.0
four2 real8 4.0

ten real8 1000.0
num real8 10.0


.code                                   
main PROC       
    finit 
    mWrite "Enter coefficient (a): "
    call ReadFloat
    fst a
    fstp a2

    mWrite "Enter coefficient (b): "
    call ReadFloat
    fst b
    fstp b2

    mWrite "Enter coefficient (c): "
    call ReadFloat
    fst cc
    fstp cc2
    
    mWrite "Roots: "
    call root1
    call Crlf
    call root2

    ;call showfpustack
exit
main ENDP

root1 PROC
    
    ; b^2
    fld b
    fmul b
    fchs    ; flip sign
    fst b
    
    ; 4 * a * c
    fld four
    fmul a
    fmul cc
    fchs
    fsub b 
    fsqrt
    fst four


    fld b
    fchs
    fsqrt
    

    fchs
    fadd four
    fst b

    fld two
    fmul a
    fst two


    fld b
    fdiv two

    call WriteFloat
    call showfpustack

    ret
root1 endp

root2 PROC

    fld b2
    fmul b2
    fchs
    fst b2


    fld four2
    fmul a2
    fmul cc2
    fchs
    fsub b2
    fsqrt
    fst four2


    fld b2
    fchs
    fsqrt


    fchs
    fsub four2
    fst b2

    call Crlf
    call WriteFloat

    fld two2
    fmul a2
    fst two2

    fld b2
    fdiv two2

    call showfpustack
    ret
root2 endp
end main

Мне удалось проверить результаты предыдущих расчетов. Вот только с этой частью у меня проблемы.


person Nico    schedule 19.11.2020    source источник
comment
Вместо fmul на 2 вы можете просто fadd st0 удвоить текущее значение вершины стека. Но в любом случае, когда вы делаете один шаг в отладчике, где появляется NaN? Вы берете sqrt отрицательного числа? Или вы переполняете стек всеми этими fld нажатиями стека и не выталкиваете? Кроме того, зачем вам перезаписывать константу 4.0 результатом fsqrt? Это странно и означает, что вы не можете снова вызвать свою функцию.   -  person Peter Cordes    schedule 19.11.2020
comment
Я все еще получаю NaN. st(0) отсутствует, когда я вызываю showFPUstack.   -  person Nico    schedule 19.11.2020
comment
А не от WriteFloat? Изменяет ли WriteFloat регистры FP? Используйте отладчик, чтобы выяснить это, вместо того, чтобы тратить время только на вызов функций печати. Или вы имеете в виду root2, который использует только showfpustack?   -  person Peter Cordes    schedule 19.11.2020
comment
st(0) становится 1#IND, когда я загружаю в него a2. Любая идея, почему это происходит?   -  person Nico    schedule 19.11.2020
comment
Вы имеете в виду с fmul a2? Это не просто загружается, это st0 *= a2. Но в любом случае, каковы были старые значения st0 и a2 на тот момент? Вы уверены, что он не попал в NaN в fld two2? Этого можно было бы ожидать, если бы все восемь st0 .. st7 регистров стека x87 уже использовались до загрузки; как я уже сказал, потому что ты никогда ничего не хлопаешь.   -  person Peter Cordes    schedule 19.11.2020
comment
Вы также можете гораздо лучше использовать стек x87 вместо того, чтобы постоянно сохранять в память и перезагружать, и такие вещи, как fdivr b2 для выполнения st0 = b2 / st0 вместо этого store/load/fdiv. Или вы могли бы fld b2 / fxchg / fdivp или что-то в этом роде, но это явно хуже. Как я уже сказал, вам не нужно перезаписывать свои константы, это просто сбивает с толку любого, кто читает ваш код.   -  person Peter Cordes    schedule 19.11.2020


Ответы (1)


Вместо того, чтобы размышлять о том, почему вы получаете NAN, я покажу вам код, который вам нужно написать, чтобы вычислить корни квадратного уравнения.
Вы видите, что вам не нужны никакие константы в памяти. вам также не нужно несколько копий входных данных a, b и c.

Сброс среды FPU

fninit

Сохраните интересную константу, которую мы будем использовать несколько раз:

fld     a             ; (st0) a
fadd    st0           ; (st0) a + a == 2a

Вычисление D = b^2 - 4ac

fld     b             ; (st0) b                 (st1) 2a
fmul    st0           ; (st0) b * b == b^2      (st1) 2a

fld     c             ; (st0) c                 (st1) b^2  (st2) 2a
fmul    st2           ; (st0) c * 2a == 2ac     (st1) b^2  (st2) 2a
fadd    st0           ; (st0) 2ac + 2ac == 4ac  (st1) b^2  (st2) 2a

fsubp                 ; (st0) b^2 - 4ac == D    (st1) 2a

Здесь нам нужно проверить, не является ли D отрицательным, потому что для D<0 не существует корней.

ftst                  ; This compares st0 to 0.0 and sets flags C3, C2, and C0
fnstsw  ax            ; Copies those flags to AX
sahf                  ; Copies those flags to EFLAGS
jp      IsUnordered   ; This should not happen
jc      IsNegative    ; This is very possible

Мы можем извлечь квадратный корень, только если D неотрицательно.

fsqrt                 ; (st0) sqrt(D)                   (st1) 2a

Переходим к 1-му корню R1:

fld     b             ; (st0) b                         (st1) sqrt(D)  (st2) 2a
fchs                  ; (st0) -b                        (st1) sqrt(D)  (st2) 2a
fsub    st1           ; (st0) -b - sqrt(D)              (st1) sqrt(D)  (st2) 2a
fdiv    st2           ; (st0) (-b - sqrt(D)) / 2a == R1 (st1) sqrt(D)  (st2) 2a

Переходим ко второму корню R2:

fld     b             ; (st0) b                         (st1) R1  (st2) sqrt(D)  (st3) 2a
fchs                  ; (st0) -b                        (st1) R1  (st2) sqrt(D)  (st3) 2a
fadd    st2           ; (st0) -b - sqrt(D)              (st1) R1  (st2) sqrt(D)  (st3) 2a
fdiv    st3           ; (st0) (-b - sqrt(D)) / 2a == R2 (st1) R1  (st2) sqrt(D)  (st3) 2a

На данный момент стек регистров FPU имеет только 4 записи:

st3 = 2a
st2 = sqrt(D)
st1 = R1
st0 = R2

Обратите внимание, что нигде в этом коде мне не нужно было ничего сохранять в памяти. Работа с FPU — это вопрос тщательного планирования. Перекомпоновка выражения часто выгодна, а повторное использование ранее вычисленных значений всегда является выигрышем.

person Sep Roland    schedule 19.11.2020
comment
Вам не нужен fninit, если ваш другой код не содержит ошибок, поэтому стек x87 уже пуст. Но, может быть, если вы не уверены, что код CRT не испортил настройку точности, вы захотите сбросить ее? fninit в начале вычислений обычно является признаком плохого кода, который не уравновешивает стек x87. - person Peter Cordes; 19.11.2020
comment
Было бы неплохо показать, как использовать комментарии для отслеживания того, что находится в стеке FP на каждом этапе, поскольку вы пытаетесь показать, что это то, что вы должны делать вместо сохранения и перезагрузки. Отмечая содержимое стека x87 в комментариях к каждой инструкции, вы записываете это планирование и определяете, на какой st регистр ссылаться. - person Peter Cordes; 19.11.2020
comment
fld b / fchs / fadd st1 - разве нельзя просто fsubr b сделать st0 -= b? То же самое для первого корня, fchs/fsubr. О, я вижу, вы делаете оба корня в одной и той же функции. Таким образом, вы можете fld b / fchs / fld st0 отправить копию инвертированного b. - person Peter Cordes; 19.11.2020
comment
@PeterCordes У меня сложилось впечатление, что мои комментарии уже показали, что было в каком регистре FPU. И для этого короткого расчета, я думаю, этого может быть достаточно. Использование инструкций reverse всегда немного сложно, поэтому я их не использовал. - person Sep Roland; 19.11.2020
comment
Вы показываете, что находится в st0. Иногда я писал каждый комментарий в виде разделенного , или ; списка полного содержимого стека x87 на каждом этапе, что, я думаю, может помочь новичку визуализировать работу стека. например в корне куба на x87 FPU с использованием метода Ньютона-Рафсона. Это может легко стать шумным :/. Но OP, похоже, не понимает стек x87, учитывая отсутствие всплывающих окон и безумное хранилище / перезагрузку вместо fxch, fsubr или w / e, что, вероятно, приводит к загрузке NaN в уже заполненный регистр. Так что я думаю, что это может помочь. - person Peter Cordes; 19.11.2020
comment
Хорошее редактирование, комментарии здесь прекрасно работают. Возможно, хорошо показать использование ffree st(2) и ffree st(3) для очистки стека x87. Или fstp результаты, затем fstp st(0) дважды. Или fcompp для двойного всплывающего окна. Очистка стека x87 — важная часть написания функции, которая не нуждается в более поздних функциях для использования fninit. - person Peter Cordes; 19.11.2020