Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 301
gordon sung Avatar asked May 25 '17 03:05

gordon sung


People also ask

What does Invalid floating point mean?

Invalid floating point operation (invalid floating point operation prompt) appears when the computer is turned on, which means that the conflicting software needs to be uninstalled in safe mode due to the conflict between the program installed on the computer and the system.


2 Answers

With thanks to Michael Petch

Your bug lies not in the computation itself, which in MASM is correct, but in your printing of the result. For printing floating-point numbers, writeint is incorrect; You should use WriteFloat, which has its own calling convention to be followed.

WriteFloat accepts a single float in st(0) and prints it to console [1]. It does not pop the value from the x87 stack.

Therefore, immediately after your FPU code, you should add

fld  posx
call WriteFloat
call Crlf
fld  negx
call WriteFloat
call Crlf
emms

You should also have the correct MASM includes at the beginning. Something akin to:

INCLUDE     irvine32.inc
INCLUDE     floatio.inc
INCLUDE     macros.inc
INCLUDELIB  kernel32.lib
INCLUDELIB  user32.lib
INCLUDELIB  Irvine32.lib

Lacking MASM on my machine, I rewrote your program as a GNU C program with inline assembly, and besides each instruction put in a comment the state of the floating-point stack at that moment.

#include <stdio.h>


int main(void){
    asm(
    ".intel_syntax\n"
    ".data\n"
    "a:     .single 3.0\n"
    "b:     .single 7.0\n"
    "cc:    .single 2.0\n"
    "posx:  .single 0.0\n"
    "negx:  .single 0.0\n"

    ".text\n"
    "fld1\n"                     // [1]
    "fadd    %st, %st\n"         // [2]
    "fld     %st\n"              // [2,                     2]
    "fmul    dword ptr a\n"      // [2a,                    2]

    "fmul    %st(1), %st\n"      // [2a,                    4a]
    "fxch\n"                     // [4a,                    2a]
    "fmul    dword ptr cc\n"     // [4ac,                   2a]

    "fld     dword ptr b\n"      // [b,                     4ac,              2a]
    "fmul    %st, %st\n"         // [b^2,                   4ac,              2a]
    "fsubrp\n"                   // [b^2-4ac,               2a]
    "fsqrt\n"                    // [sqrt(b^2-4ac),         2a]
    "fld     dword ptr b\n"      // [b,                     sqrt(b^2-4ac),    2a]
    "fchs\n"                     // [-b,                    sqrt(b^2-4ac),    2a]
    "fxch\n"                     // [sqrt(b^2-4ac),         -b,               2a]

    "fld     %st\n"              // [sqrt(b^2-4ac),            sqrt(b^2-4ac), -b, 2a]
    "fadd    %st, %st(2)\n"      // [-b+sqrt(b^2-4ac),         sqrt(b^2-4ac), -b, 2a]
    "fxch\n"                     // [   sqrt(b^2-4ac),      -b+sqrt(b^2-4ac), -b, 2a]
    "fsubp   %st(2), %st\n"      // [-b+sqrt(b^2-4ac),      -b-sqrt(b^2-4ac), 2a]

    "fdiv    %st, %st(2)\n"      // [(-b+sqrt(b^2-4ac))/2a, -b-sqrt(b^2-4ac), 2a]
    "fstp    dword ptr posx\n"   // [ -b-sqrt(b^2-4ac),     2a]
    "fdivrp\n"                   // [(-b-sqrt(b^2-4ac))/2a]
    "fstp    dword ptr negx\n"   // []

    ".att_syntax\n"
    );

    extern float posx, negx;

    printf("posx: %+0.17f\nnegx: %+0.17f\n", posx, negx);

    return 0;
}

Printing

posx: -0.33333334326744080
negx: -2.00000000000000000

Which is correct:

  • 3*(-1/3)^2 + 7*(-1/3) + 2 = 3/9 - 7/3 + 2 = 1/3-7/3+2 = -6/3+2 = -2+2 = 0
  • 3*(-2)^2 + 7*(-2) + 2 = 3*4 -14 + 2 = 12-14+2 = -2+2 = 0

[1] § 12.2.7 Reading and Writing Floating-Point Values

like image 43
Iwillnotexist Idonotexist Avatar answered Oct 06 '22 21:10

Iwillnotexist Idonotexist


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
like image 162
rkhb Avatar answered Oct 06 '22 23:10

rkhb