【问题标题】:Keep getting NaN after doing some calculations in assembly在汇编中进行一些计算后继续获取 NaN
【发布时间】:2020-11-19 06:36:18
【问题描述】:

我正在开发一个求解二次方程根的程序。我能够在 root1 子例程中获得第一个根。但是,当我尝试求解 root2 中的第二个根时,二次公式的“/2a”部分一直产生 NaN。

代码如下:

INCLUDE Irvine32.inc    
INCLUDE macros.inc

.data                                                   
a real8 ?
b real8 ?
cc real8 ?

a2 real8 ?
b2 real8 ?
cc2 real8 ?

two real8 2.0
four real8 4.0

two2 real8 2.0
four2 real8 4.0

ten real8 1000.0
num real8 10.0


.code                                   
main PROC       
    finit 
    mWrite "Enter coefficient (a): "
    call ReadFloat
    fst a
    fstp a2

    mWrite "Enter coefficient (b): "
    call ReadFloat
    fst b
    fstp b2

    mWrite "Enter coefficient (c): "
    call ReadFloat
    fst cc
    fstp cc2
    
    mWrite "Roots: "
    call root1
    call Crlf
    call root2

    ;call showfpustack
exit
main ENDP

root1 PROC
    
    ; b^2
    fld b
    fmul b
    fchs    ; flip sign
    fst b
    
    ; 4 * a * c
    fld four
    fmul a
    fmul cc
    fchs
    fsub b 
    fsqrt
    fst four


    fld b
    fchs
    fsqrt
    

    fchs
    fadd four
    fst b

    fld two
    fmul a
    fst two


    fld b
    fdiv two

    call WriteFloat
    call showfpustack

    ret
root1 endp

root2 PROC

    fld b2
    fmul b2
    fchs
    fst b2


    fld four2
    fmul a2
    fmul cc2
    fchs
    fsub b2
    fsqrt
    fst four2


    fld b2
    fchs
    fsqrt


    fchs
    fsub four2
    fst b2

    call Crlf
    call WriteFloat

    fld two2
    fmul a2
    fst two2

    fld b2
    fdiv two2

    call showfpustack
    ret
root2 endp
end main

我能够验证之前的计算结果。这只是我遇到问题的部分。

【问题讨论】:

  • 而不是fmul 2,你可以fadd st0 将当前栈顶值加倍。但是无论如何,当您单步执行调试器时,NaN 会出现在哪里?你在取负数的 sqrt 吗?或者你是否用所有那些fld 堆栈推送而没有弹出堆栈溢出?另外,你为什么要用 fsqrt 结果覆盖你的常量4.0?这很奇怪,意味着你不能再次调用你的函数。
  • 我仍然得到 NaN。调用 showFPUstack 时缺少 st(0)。
  • 但不是来自WriteFloatWriteFloat 是否修改 FP 寄存器?使用调试器来查找,而不是浪费时间只调用打印函数。或者你的意思是root2,它只使用showfpustack
  • 当我将 a2 加载到 st(0) 上时,它变为 1#IND 。知道为什么会这样吗?
  • 您还可以更好地利用 x87 堆栈,而不是一直存储到内存并重新加载,以及像 fdivr b2 这样的东西来执行 st0 = b2 / st0 而不是存储/加载/fdiv。或者你可以fld b2 / fxchg / fdivp 或其他东西,但这显然更糟。就像我说的,你不需要覆盖你的常量,这会让任何阅读你代码的人感到困惑。

标签: assembly masm irvine32 x87


【解决方案1】:

我将引导您完成计算二次方程的根所需编写的代码,而不是推测您为什么会得到 NAN。
您会发现您不需要内存中的任何常量,也不需要输入 abc 的多个副本。

重置 FPU 环境

fninit

存储一个有趣的常量,我们将重复使用多次:

fld     a             ; (st0) a
fadd    st0           ; (st0) a + a == 2a

计算 D = b^2 - 4ac

fld     b             ; (st0) b                 (st1) 2a
fmul    st0           ; (st0) b * b == b^2      (st1) 2a

fld     c             ; (st0) c                 (st1) b^2  (st2) 2a
fmul    st2           ; (st0) c * 2a == 2ac     (st1) b^2  (st2) 2a
fadd    st0           ; (st0) 2ac + 2ac == 4ac  (st1) b^2  (st2) 2a

fsubp                 ; (st0) b^2 - 4ac == D    (st1) 2a

这里我们需要测试 D 是否不是负数,因为 D<0 不存在根。

ftst                  ; This compares st0 to 0.0 and sets flags C3, C2, and C0
fnstsw  ax            ; Copies those flags to AX
sahf                  ; Copies those flags to EFLAGS
jp      IsUnordered   ; This should not happen
jc      IsNegative    ; This is very possible

只有当 D 不为负时,我们才能取平方根。

fsqrt                 ; (st0) sqrt(D)                   (st1) 2a

寻找第一个根 R1:

fld     b             ; (st0) b                         (st1) sqrt(D)  (st2) 2a
fchs                  ; (st0) -b                        (st1) sqrt(D)  (st2) 2a
fsub    st1           ; (st0) -b - sqrt(D)              (st1) sqrt(D)  (st2) 2a
fdiv    st2           ; (st0) (-b - sqrt(D)) / 2a == R1 (st1) sqrt(D)  (st2) 2a

寻找第二根 R2:

fld     b             ; (st0) b                         (st1) R1  (st2) sqrt(D)  (st3) 2a
fchs                  ; (st0) -b                        (st1) R1  (st2) sqrt(D)  (st3) 2a
fadd    st2           ; (st0) -b - sqrt(D)              (st1) R1  (st2) sqrt(D)  (st3) 2a
fdiv    st3           ; (st0) (-b - sqrt(D)) / 2a == R2 (st1) R1  (st2) sqrt(D)  (st3) 2a

此时FPU寄存器栈只有4个入口:

st3 = 2a
st2 = sqrt(D)
st1 = R1
st0 = R2

请注意,在这段代码中,我不必将任何内容存储回内存中。与 FPU 合作需要仔细规划。重新排列表达式通常是有利的,并且重新使用以前计算的值总是一种胜利。

【讨论】:

  • 如果您的其他代码没有问题,则不需要fninit,因此 x87 堆栈已经为空。但也许如果您不相信 CRT 代码没有对精度设置进行限制,您会想要重置它吗? fninit 在计算开始时通常表示代码不平衡 x87 堆栈。
  • 展示如何使用 cmets 在每一步跟踪 FP 堆栈中的内容可能会很好,因为您试图表明这是您应该做的,而不是存储并重新加载。注意每条指令的 cmets 中的 x87 堆栈内容是您如何记录该计划,以及如何确定要引用哪个 st 寄存器。
  • fld b / fchs / fadd st1 - 这不能简单地用fsubr b 来做st0 -= b 吗?第一个根相同,fchs / fsubr。哦,我看到你在同一个函数中做这两个根。所以你可以fld b / fchs / fld st0 推送被否定的b 的副本。
  • @PeterCordes 我的印象是我的 cmets 已经显示了 FPU 寄存器中的内容。对于这个简短的计算,我认为它就足够了。 reverse 指令的使用总是有点棘手,所以我没有使用它们。
  • 您正在展示st0 中的内容。有时我会将每条评论写成,;-分隔的每个步骤中完整x87 堆栈内容的列表,我认为这有助于帮助初学者可视化堆栈操作。例如在Cube root on x87 FPU using Newton-Raphson method。它很容易变得嘈杂:/。但是 OP 似乎缺乏对 x87 堆栈的理解,因为缺少弹出和疯狂的存储/重新加载而不是 fxch 或 fsubr 或 w/e,可能导致 NaN 加载到已经满的寄存器中。所以我认为这可能会有所帮助。
猜你喜欢
  • 2011-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-09
  • 2013-09-03
  • 1970-01-01
相关资源
最近更新 更多