浮点程序给出无效结果
Floating Point Program gives Invalid Result
最近编辑
我正尝试在 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 库中。有人可以告诉我为什么它不起作用以及需要修复什么吗?谢谢。
感谢 Michael Petch
您的错误不在于计算本身,这在 MASM 中是正确的,而在于您打印结果。对于打印浮点数,writeint
是不正确的;您应该使用 WriteFloat
,它有自己的调用约定。
WriteFloat
在 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 读写浮点值
浮点数在特殊处理器(FPU)的特殊寄存器中处理并以特殊格式存储,不能被视为整数(WriteInt
)。浮点数包含有关数字的更多信息,例如符号和指数。数字本身被更改为 1 到 2 之间的数字,并带有适当的指数,其中前导 1 通常被隐藏。在这里查看双精度格式:https://en.wikipedia.org/wiki/Double-precision_floating-point_format。这些数字不太可能准确。
至少从 11 年开始,Irvine32 库提供了函数 WriteFloat
以指数形式显示 FPU 寄存器 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 (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.
如果您不能使用提供浮点例程的库,您必须自己转换数字:
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
最近编辑
我正尝试在 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 库中。有人可以告诉我为什么它不起作用以及需要修复什么吗?谢谢。
感谢 Michael Petch
您的错误不在于计算本身,这在 MASM 中是正确的,而在于您打印结果。对于打印浮点数,writeint
是不正确的;您应该使用 WriteFloat
,它有自己的调用约定。
WriteFloat
在 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 读写浮点值
浮点数在特殊处理器(FPU)的特殊寄存器中处理并以特殊格式存储,不能被视为整数(WriteInt
)。浮点数包含有关数字的更多信息,例如符号和指数。数字本身被更改为 1 到 2 之间的数字,并带有适当的指数,其中前导 1 通常被隐藏。在这里查看双精度格式:https://en.wikipedia.org/wiki/Double-precision_floating-point_format。这些数字不太可能准确。
至少从 11 年开始,Irvine32 库提供了函数 WriteFloat
以指数形式显示 FPU 寄存器 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 (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.
如果您不能使用提供浮点例程的库,您必须自己转换数字:
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