assemblymasmirvine32x87

Keep getting NaN after doing some calculations in assembly


I'm working on a program that solves for the roots of a quadratic equation. I was able to get the first root in the root1 subroutine. However, when I try to solve for the second root in root2, the "/2a" part of the quadratic formula keeps yielding NaN.

Here's the code:

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

I was able to verify the results of previous calculations. It's just this part that I'm having trouble with.


Solution

  • Instead of speculating about why you get a NAN, I'll walk you through the code that you need to write in order to calculate the roots of the quadratic equation.
    You see that you don't need any constants in memory nor do you need multiple copies of the inputs a, b, and c.

    Reset the FPU environment

    fninit
    

    Store an interesting constant that we will re-use muliple times:

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

    Calculating 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
    

    Here we need to test if D is not negative because no roots exist for 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
    

    We can take the square root only if D is not negative.

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

    Going for the 1st root 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
    

    Going for the 2nd root 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
    

    At this point the FPU register stack has only 4 entries:

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

    Please note that nowhere in this code did I have to store anything back in memory. Working with the FPU is a matter of careful planning. Re-arranging an expression is often advantageous and re-using previously calculated values is always a win.