Подтвердить что ты не робот

Программа с плавающей запятой дает неверный результат

ПОСЛЕДНИЕ ИЗОБРАЖЕНИЯ

Я пытаюсь запустить эту программу Quadratic Equation с плавающей запятой на x86 MASM. Этот код содержится в учебнике Kip Irvine x86, и я хочу посмотреть, как он работает визуально. Ниже приведен следующий код:

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

Итак, я смог полностью скомпилировать, выполнить и полностью выполнить свою программу. Однако, всякий раз, когда я запускаю программу, я получаю это как результат:

+1694175115

Почему результат такой большой? Также я попробовал называть writefloat, но он говорит, что эта процедура не находится в библиотеке Irvine32.inc или Macros.inc. Может кто-нибудь показать мне, почему он не работает и что нужно исправить? Благодарю.

4b9b3361

Ответ 1

Числа с плавающей запятой обрабатываются в специальных регистрах в специальном процессоре (FPU) и сохраняются в специальном формате и не могут рассматриваться как целые числа (WriteInt). Числа с плавающей запятой содержат дополнительную информацию о знаке числа и экспоненте. Само число изменяется на число от 1 до 2 с соответствующим показателем, где ведущий 1 обычно скрыт. Посмотрите здесь только для двойного формата: https://en.wikipedia.org/wiki/Double-precision_floating-point_format. Эти цифры вряд ли будут точными.

По крайней мере, начиная с 11 лет библиотека Irvine32 предоставляет функцию WriteFloat, чтобы показать значение регистра F0U ST0 в экспоненциальной форме. Он не всплывает или не освобождает этот регистр.

Изменить

call writeint

к

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

Если в вашей библиотеке нет WriteFloat, я предлагаю загрузить и установить новейшие файлы с домашней страницы Irvine: http://www.kipirvine.com/asm/examples/index.htm ( Примеры программ и исходный код библиотеки ссылок для Седьмой версии). Вы также можете использовать другую библиотеку, например. C-runtime library (msvcrt.inc и msvcrt.lib) или Библиотека FPU Раймонда Филиатре.

Если вы не можете использовать библиотеку, которая предоставляет процедуры с плавающей запятой, вам необходимо самостоятельно преобразовать числа:

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

Ответ 2

С благодарностью Майкл Пётч

Ваша ошибка заключается не в самом вычислении, которое в MASM правильное, но в вашей печати результата. Для печати чисел с плавающей запятой writeint неверно; Вы должны использовать WriteFloat, который должен иметь свое собственное соглашение о вызове.

WriteFloat принимает одно значение float в st(0) и выводит его на консоль [1]. Он не выводит значение из стека x87.

Поэтому сразу после вашего кода FPU вы должны добавить

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

У вас также должен быть правильный MASM, включенный в начале. Что-то похожее на:

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

Отсутствие MASM на моей машине, я переписал вашу программу как программу GNU C с встроенной сборкой, и, кроме того, каждая команда положила в комментарий состояние стека с плавающей запятой в этот момент.

#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;
}

Печать

posx: -0.33333334326744080
negx: -2.00000000000000000

Что правильно:

  • 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 Чтение и запись значений с плавающей запятой