Программа с плавающей запятой дает недопустимый результат
ПОСЛЕДНИЕ ИЗМЕНЕНИЯ
Я пытаюсь запустить эту программу квадратичного уравнения с плавающей запятой на 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 или макросы.библиотека inc. Может кто-нибудь показать мне, почему он не работает и что нужно исправить? Спасибо.
2 ответов
числа с плавающей запятой обрабатываются в специальных регистрах в специальном процессоре (FPU) и хранятся в специальном формате и не могут рассматриваться как целые числа (WriteInt
). Числа с плавающей запятой содержат дополнительную информацию о числе, таком как знак и показатель. Само число изменяется на число от 1 до 2 с соответствующим показателем, где ведущее 1 обычно скрыто. Посмотрите здесь только для двойного формата: https://en.wikipedia.org/wiki/Double-precision_floating-point_format. Эти цифры вряд ли будут точными.
по крайней мере, с 11 лет библиотека Irvine32 предоставляет функцию WriteFloat
чтобы показать значение регистра FPU ST0 в экспоненциальной форме. Это не поп или бесплатно, что регистрация.
изменить
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
если ваша библиотека не имеет WriteFloat
Я предлагаю загрузить и установить самые новые файлы из Домашняя страница Ирвина:http://www.kipirvine.com/asm/examples/index.htm (пример программы и ссылка библиотеки исходного кода для седьмого издания). Вы также можете использовать другую библиотеку, например библиотеку C-runtime (msvcrt.inc и msvcrt.Либ) или Раймон Filiatreault библиотека.
если вы не можете использовать библиотеку, которая предоставляет процедуры с плавающей запятой, вы должны преобразовать числа самостоятельно:
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
С благодарностью Майклу Петчу
ваша ошибка заключается не в самом вычислении, которое в MASM правильно, а в вашей печати результата. Для печати чисел с плавающей запятой,writeint
это неверно; вы должны использовать WriteFloat
, который имеет свое собственное вызывающее соглашение, которому нужно следовать.
WriteFloat
принимает один поплавок в st(0)
и печатает его на консоли [1]. Это не pop значение из 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 чтение и запись значений с плавающей запятой