【问题标题】:Floating Point Program gives Invalid Result浮点程序给出无效的结果
【发布时间】:2017-10-25 14:28:15
【问题描述】:

近期编辑

我正在尝试在 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 库中。有人可以告诉我为什么它不起作用以及需要修复什么吗?谢谢。

【问题讨论】:

  • 您不会以任何方式结束程序,因此 CPU 将继续读取垃圾数据并执行它。使用调试器随时查看发生了什么。
  • 我没有看到这个问题的 C 部分。
  • 当您到达fstp negx 时,st(FPU 堆栈的顶部)具有二次方程的根之一。 fstp negxst 中的值并将其放入内存中的 negx 并弹出堆栈。您可以简单地删除 fstp negx 并通过简单地使用WriteFloat 函数和call WriteFloat 打印FPU 堆栈的顶部。如果您希望将值存储在negx 中并打印然后您可以将fstp negx 更改为fst negx 并使用call WriteFloat 跟随它
  • 当然要完全删除call writeint,因为它会在EAX中打印出带符号的整数。它不写入浮点值。
  • WriteFloat 必须在那里,除非你有一个真正的旧 irvine32.incirvine32.lib。获取newer link library from Irvine's homepage第七版示例程序和链接库源代码)并安装。

标签: assembly floating-point x86 masm irvine32


【解决方案1】:

感谢迈克尔·佩奇

您的错误不在于计算本身,这在 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 读取和写入浮点值

【讨论】:

  • 您使用的汇编程序与 OP 不同。您正在使用使用 Intel 语法的 GCC 内联汇编程序。 OPs 代码确实可以工作(除了 writeint 并且是正确的语法。它使用 32 位 MASM(Microsoft Assembler)和 Irvine32 库(它是来自一本著名的汇编语言书籍的库)。MASM 与其他汇编器不同具有有限的类型检查。从所有使用 REAL4 的声明中知道每个变量的大小,它是 32 位(DWORD 的大小)。MASM 将自动确定(在可以的情况下)声明中内存操作数的大小跨度>
  • 在查看 OPs 代码的结构方式时,FPU 堆栈确实在最终的fstp negx 之后返回到清晰状态。这是因为 MASM 会将fsubr 编码为fsubrp st(1),stfdivr 编码为fdivrp st(1),st
  • @MichaelPetch 好的,我明白了。但在那种情况下,我自己的程序是 MASM 的忠实 GAS 复制品,而我对 x87 堆栈上的活动的评论正是 OP 认为他应该看到的。 FPU代码没有bug,只有显示代码。
  • 正如我在代码中所说,实际上计算正确。但是 OP 也在询问如何将浮点数写入显示器。他最初将这个问题标记为 MASM 而不是 GNU Assembler/GCC。他正在使用著名的 Irvine32 库(有人好心地进来并稍后标记它)。他的失败很大程度上是因为不正确地使用了WriteInt 而不是WriteFloat(他只需要在他的代码中输入两次WriteFloat 来打印两个根)。他的未定义错误很可能是因为他没有将 Irvine32.lib 库链接进去。
  • 因此,尽管您提供了答案,但它确实没有回答 OP 提出的问题。 OPs 代码已经是语法正确且可在 MASM 中使用的最小完整示例。
【解决方案2】:

浮点数在特殊处理器 (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第七版的示例程序和链接库源代码)。您还可以使用另一个库,例如C 运行时库(msvcrt.inc 和 msvcrt.lib)或 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-31
    • 1970-01-01
    • 2016-12-25
    • 1970-01-01
    • 2014-07-12
    • 2022-09-27
    • 1970-01-01
    相关资源
    最近更新 更多