Search code examples
assemblyfloating-pointx86masmirvine32

Floating Point Program gives Invalid Result


RECENT EDIT

I am trying to run this floating point Quadratic Equation program on x86 MASM. This code is found in the Kip Irvine x86 textbook and I want to see how it works visually. The following code is below:

include irvine32.inc 
.DATA
a REAL4 3.0
b REAL4 7.0
cc REAL4 2.0
posx REAL4 0.0
negx REAL4 0.0

.CODE


main proc 
; Solve quadratic equation - no error checking
; The formula is: -b +/- squareroot(b2 - 4ac) / (2a)
fld1 ; Get constants 2 and 4
fadd st,st ; 2 at bottom
fld st ; Copy it
fmul a ; = 2a

fmul st(1),st ; = 4a
fxch ; Exchange
fmul cc ; = 4ac

fld b ; Load b
fmul st,st ; = b2
fsubr ; = b2 - 4ac
; Negative value here produces error
fsqrt ; = square root(b2 - 4ac)
fld b ; Load b
fchs ; Make it negative
fxch ; Exchange

fld st ; Copy square root
fadd st,st(2) ; Plus version = -b + root(b2 - 4ac)
fxch ; Exchange
fsubp st(2),st ; Minus version = -b - root(b2 - 4ac)

fdiv st,st(2) ; Divide plus version
fstp posx ; Store it
fdivr ; Divide minus version
fstp negx ; Store it

call writeint
exit 
main endp 
end main

So I was able to have my program compile, execute and work completely. However, Whenever I run the program, I get this as the result:

+1694175115

Why is the result so large? Also I tried calling writefloat but it says this procedure is not in the Irvine32.inc or Macros.inc Library. Can someone show me why is it not working and what needs to be fixed? Thanks.


Solution

  • Floating point numbers are processed in special registers in a special processor (FPU) and stored in a special format and cannot be treated as integers (WriteInt). Floating point numbers contain further information about the number like sign and exponent. The number itself is changed to a number between 1 and 2 with an appropriate exponent, where the leading 1 is normally hidden. Take a look here just for the double format: https://en.wikipedia.org/wiki/Double-precision_floating-point_format. These numbers are unlikely to be precise.

    At least since 11 years the Irvine32 library provides the function WriteFloat to show the value of the FPU register ST0 in exponential form. It does not pop or free that register.

    Change

    call writeint
    

    to

    fld posx                ; Load floating point number into ST0
    call WriteFloat         ; Write ST0
    ffree st[0]             ; Free ST0 - don't forget it!
    call Crlf               ; New line
    fld negx                ; Load floating point number into ST0
    call WriteFloat         ; Write ST0
    ffree st[0]             ; Free ST0 - don't forget it!
    call Crlf               ; New line
    

    If your library don't have WriteFloat I suggest to download and install the newest files from Irvine's homepage: http://www.kipirvine.com/asm/examples/index.htm (Example programs and link library source code for the Seventh Edition). You can also use another library, e.g. the C-runtime library (msvcrt.inc and msvcrt.lib) or Raymond Filiatreault's FPU library.

    If you can't use a library that provides floating point routines you have to convert the numbers by yourself:

    INCLUDE irvine32.inc
    
    .DATA
        a REAL4 3.0
        b REAL4 7.0
        cc REAL4 2.0
        posx REAL4 0.0
        negx REAL4 0.0
    
        buf BYTE 1024 DUP (?)
    
    .CODE
    
    double2dec PROC C USES edi              ; Args: ST(0): FPU-register to convert, EDI: pointer to string
    LOCAL CONTROL_WORD:WORD, TEN:WORD, TEMP:WORD, DUMMY:QWORD
    
        ; modifying rounding mode
        fstcw CONTROL_WORD
        mov ax, CONTROL_WORD
        or ah, 00001100b            ; Set RC=11: truncating rounding mode
        mov TEMP, ax
        fldcw TEMP                  ; Load new rounding mode
    
        ; Check for negative
        ftst                        ; ST0 negative?
        fstsw ax
        test ah, 001b
        jz @F                       ; No: skip the next instructions
        mov byte ptr [edi], '-'     ; Negative sign
        add edi, 1
        @@:
        FABS                        ; Abs (upper case to differ from C-library)
    
        ; Separate integer and fractional part & convert integer part into ASCII
        fst st(1)                   ; Doubling ST(0) - ST(1)=ST(0)
        frndint                     ; ST(0) to integer
        fsub st(1), st(0)           ; Integral part in ST(0), fractional part in ST(1)
    
        ; Move 10 to st(1)
        mov TEN, 10
        fild TEN
        fxch
    
        xor ecx, ecx                ; Push/pop counter
    
        @@:                         ; First loop
        fst st(3)                   ; Preserve ST(0)
        fprem                       ; ST(0) = remainder ST(0)/ST(1)
        fistp word ptr TEMP         ; ST(3) -> ST(2) !
        push word ptr TEMP
        inc ecx
        fld st(2)                   ; Restore ST(0)
        fdiv st(0), st(1)
        frndint                     ; ST(0) to integer
        fxam                        ; ST0 == 0.0?
        fstsw ax
        sahf
        jnz @B                      ; No: loop
    
        fxch st(2)                  ; ST(0) <-> ST(2) (fractional part)
        ffree st(2)
        ffree st(3)
    
        @@:                         ; Second loop
        pop ax
        or al, '0'
        mov [edi], al
        inc edi
        loop @B                     ; Loop ECX times
    
        mov byte ptr [edi], '.'     ; Decimal point
        add edi, 1
    
        ; Isolate digits of fractional part and store ASCII
        get_fractional:
        fmul st(0), st(1)           ; Multiply by 10 (shift one decimal digit into integer part)
        fist word ptr TEMP          ; Store digit
        fisub word ptr TEMP         ; Clear integer part
        mov al, byte ptr TEMP       ; Load digit
        or al, 30h                  ; Convert digit to ASCII
        mov byte ptr [edi], al      ; Append it to string
        add edi, 1                  ; Increment pointer to string
        fxam                        ; ST0 == 0.0?
        fstsw ax
        sahf
        jnz get_fractional          ; No: once more
        mov byte ptr [edi], 0       ; Null-termination for ASCIIZ
    
        ; clean up FPU
        ffree st(0)                 ; Empty ST(0)
        ffree st(1)                 ; Empty ST(1)
        fldcw CONTROL_WORD          ; Restore old rounding mode
    
        ret                         ; Return: EDI points to the null-terminated string
    double2dec ENDP
    
    
    main proc
        ; Solve quadratic equation - no error checking
        ; The formula is: -b +/- squareroot(b2 - 4ac) / (2a)
        fld1 ; Get constants 2 and 4
        fadd st,st ; 2 at bottom
        fld st ; Copy it
        fmul a ; = 2a
    
        fmul st(1),st ; = 4a
        fxch ; Exchange
        fmul cc ; = 4ac
    
        fld b ; Load b
        fmul st,st ; = b2
        fsubr ; = b2 - 4ac
        ; Negative value here produces error
        fsqrt ; = square root(b2 - 4ac)
        fld b ; Load b
        fchs ; Make it negative
        fxch ; Exchange
    
        fld st ; Copy square root
        fadd st,st(2) ; Plus version = -b + root(b2 - 4ac)
        fxch ; Exchange
        fsubp st(2),st ; Minus version = -b - root(b2 - 4ac)
    
        fdiv st,st(2) ; Divide plus version
        fstp posx ; Store it
        fdivr ; Divide minus version
        fstp negx ; Store it
    
        ; Write the results
    
        fld posx            ; Load floating point number into ST0
        lea edi, buf        ; EDI: pointer to a buffer for a string
        call double2dec     ; Convert ST0 to buf and pop
        mov edx, edi        ; EDX: pointer to a null-terminated string
        call WriteString    ; Irvine32
    
        call Crlf           ; Irvine32: New line
    
        fld negx            ; Load floating point number into ST0
        lea edi, buf        ; EDI: pointer to a buffer for a string
        call double2dec     ; Convert ST0 to buf and pop
        mov edx, edi        ; EDX: pointer to a null-terminated string
        call WriteString    ; Irvine32
    
        call Crlf           ; Irvine32: New line
    
        exit                ; Irvine32: ExitProcess
    main ENDP
    end main