使用Newton-Raphson方法在x87 FPU上的立方根

时间:2016-04-28 19:36:23

标签: assembly x86 masm x87

我正在尝试使用8086处理器编写汇编程序,该处理器将找到数字的立方根。显然我使用的是浮点数。

基于Newton-Raphson method的算法:

root := 1.0; 
repeat
     oldRoot := root;
     root := (2.0*root + x/(root*root)) / 3.0 
until ( |root – oldRoot| < 0.001;

如何将(2 * root + x)除以(root * root)?

.586
.MODEL FLAT
.STACK 4096

.DATA
root    REAL4   1.0
oldRoot REAL4   2.0
Two     REAL4   2.0
inttwo  DWORD   2
itThree DWORD   3
three   REAL4   3.0
x       DOWRD   27


.CODE
main    PROC
        finit           ; initialize FPU
        fld     root    ; root in ST
        fmul    two     ; root*two
        fadd    x       ; root*two+27

        fld     root    ; root in ST
        fimul    root    ; root*root

        mov     eax, 0  ; exit
        ret
main    ENDP 
END     

我想我不明白堆栈中的什么位置。该产品是否为行

  

fimul root;根*根

进入ST(1)?编辑不,它进入st(0)st(0)中的内容被压入堆栈到st(1)

但是我还没有想出我的问题的答案...... 我如何划分?现在我看到我需要将st(1)除以st(0)但是我不知道怎么做。我试过了。

finit           ; initialize FPU
fld     root    ; root in ST
fmul    two     ; root*two
fadd    xx      ; root*two+27
; the answer to root*two+x is stored in ST(0) when we load root st(0) moves to ST1 and we will use ST0 for the next operation

fld     root    ; root in ST previous content is now in ST1
fimul   root    ; root*root
fidiv   st(1)

编辑: 我的公式写错了。这就是我要找的。

(2.0*root) + x / (root*root)) / 3.0 That's what I need. 
STEP 1) (2 * root) 
STEP 2) x / (root * root) 
STEP 3) ADD step one and step 2 
STEP 4) divide step 3 by 3.0

root =(2.0 * 1.0)+ 27 /(1.0 * 1.0)/ 3.0; (2)+ 27 /(1.0)/ 3.0 = 11 ==&gt; root = 11

EDIT2:新代码!!

.586
.MODEL FLAT
.STACK 4096

.DATA
root    REAL4   1.0
oldRoot REAL4   2.0
Two     REAL4   2.0
three   REAL4   3.0
xx      REAL4   27.0


.CODE
main    PROC
        finit           ; initialize FPU
                fld     root    ; root in ST    ; Pass 1 ST(0) has 1.0  
repreatAgain:
        ;fld    st(2)

        fmul    two     ; root*two      ; Pass 1 ST(0) has 2                                                                            Pass 2 ST(0) = 19.333333 st(1) = 3.0 st(2) = 29.0 st(3) = 1.0

        ; the answer to roor*two is stored in ST0 when we load rootSTO moves to ST1 and we will use ST0 for the next operation
        fld     root    ; root in ST(0) previous content is now in ST(1)      Pass 1 ST(0) has 1.0 ST(1) has 2.0                        Pass 2 st(
        fmul    st(0), st(0)    ; root*root                                 ; Pass 1 st(0) has 1.0 st(1) has 2.0
        fld     xx                                                          ; Pass 1 st(0) has 27.0 st(1) has 1.0 st(2) has 2.0
        fdiv    st(0), st(1) ; x / (root*root)  ; Pass 1: 27 / 1              Pass 1 st(0) has 27.0 st(1) has 2.0 st(2) has 2.0
        fadd    st(0), st(2) ; (2.0*root) + x / (root*root))                  Pass 1 st(0) has 29.0 st(1) has 1.0 st(2) has 2.0

        fld     three                                                       ; Pass 1 st(0) has 3.0 st(1) has 29.0 st(2) has 1.0 st(3) has 2.0

        fld     st(1)                                                       ; Pass 1 st(0) has 3.0 st(1) has 29.0 st(2) = 1.0 st(3) = 2.0
        fdiv    st(0), st(1) ; (2.0*root) + x / (root*root)) / 3.0            Pass 1 st(1) has 9.6666666666667



        jmp     repreatAgain
        mov     eax, 0  ; exit
        ret
main    ENDP 
END   

2 个答案:

答案 0 :(得分:5)

英特尔insn参考手册记录了所有说明,包括fdivfdivr(x / y而不是y / x)。如果你真的需要学习大部分过时的x87(fdiv)而不是SSE2(divss),那么this x87 tutorial is essential reading,尤其是。解释寄存器堆栈的早期章节。另请参阅this x87 FP comparison Q&A。请参阅代码wiki中的更多链接。

re:EDIT2代码转储:

循环中有4条fld条指令,但没有p - 后缀操作。您的循环将在第3次迭代时溢出8寄存器FP堆栈,此时您将获得NaN。 (具体来说,不定值NaN,printf打印为1#IND

我建议设计您的循环,以便迭代从root中的st(0)开始,并以root中的下一个迭代st(0)值结束{1}} 即可。不要在循环内加载或存储到root。使用fld1在循环外加载1.0作为初始值,在循环后加载fstp [root]以将st(0)弹出到内存中。

你选择了最不方便的方法来做tmp / 3.0

                          ; stack = tmp   (and should otherwise be empty once you fix the rest of your code)
    fld     three         ; stack = 3.0, tmp
    fld     st(1)         ; stack = tmp, 3.0, tmp   ; should have used fxchg to just swap instead of making the stack deeper
    fdiv    st(0), st(1)  ; stack = tmp/3.0, 3.0, tmp

fdivfsub等具有多个注册表单:一个st(0)是目标,另一个是源。以st(0)作为来源的表单也可以p操作,因此您可以

    fld     three         ; stack = 3.0, tmp
    fdivp                 ; stack = tmp / 3.0  popping the stack back to just one entry
    ; fdivp  st(1), st(0) ; this is what fdivp with no operands means

如果直接使用内存操作数而不是加载它,它实际上甚至更简单。由于您需要st(0) /= 3.0,您可以执行fdiv [three] 。在这种情况下,FP ops就像整数运算一样,你可以div dword ptr [integer_from_memory]使用内存源操作数。

非交换操作(减法和除法)也有反向版本(例如fdivr),这可以为您节省fxchg或让您使用内存操作数,即使您已经需要3.0 / tmp而不是tmp / 3.0

除以3与乘以1/3 相同,fmulfdiv快得多。从代码简单的角度来看,乘法是可交换的,因此实现st(0) /= 3的另一种方法是:

fld    [one_third]
fmulp                  ; shorthand for  fmulp st(1), st(0)

; or
fmul   [one_third]

注意1/3.0在二进制浮点中没有精确表示,但是+/-约2 ^ 23之间的所有整数都是(单精度REAL4的尾数的大小)。如果你期望使用精确的三倍数,你应该只关心这个。

对原始代码的评论:

您可以通过提前2.0 / 3.0x/3.0将分组提升出来。如果您希望循环平均运行多次迭代,那么这是值得的。

您可以使用fld st(0)复制堆栈顶部,这样您就不必继续从内存加载。

fimul [root]整数 mul)是一个错误:您的root采用REAL4(32位浮点)格式,而不是整数。 fidiv同样是一个错误,当然也不能将x87寄存器用作源操作数。

由于您在堆栈顶部有root,我认为您可以fmul st(0)使用st(0)作为显式和隐式操作数,从而生成st(0) = st(0) * st(0) ,堆栈深度没有变化。

你也可以使用sqrt作为比1.0更好的初始近似,或者+/-1 * sqrtf(fabsf(x))。我没有看到x87指令将一个浮点数的符号应用到另一个浮点数,只是fchs无条件地翻转,fabs无条件地清除符号位。有fcmov,但需要P6或更高版本的CPU。您提到了8086,但之后使用了.586,因此IDK就是您要定位的内容。

更好的循环体:

未经过调试或测试,但您的代码充满了来自相同数据的重复加载让我发疯。这个优化版本在这里是因为我很好奇,不是因为我认为它会直接帮助OP。

此外,希望这是如何在代码中评论数据流的一个很好的例子。 (例如x87,或带有shuffle的矢量化代码)。

## x/3.0 in st(1)
## 2.0/3.0 in st(2)

# before each iteration: st(0) = root
#  after each iteration: st(0) = root * 2.0/3.0 + (x/3.0 / (root*root)), with constants undisturbed

loop_body:
    fld     st(0)         ; stack: root, root, 2/3, x/3
    fmul    st(0), st(0)  ; stack: root^2, root, 2/3, x/3
    fdivr   st(0), st(3)  ; stack: x/3 / root^2, root, 2/3, x/3
    fxchg   st(1)         ; stack: root, x/3/root^2, 2/3, x/3
    fmul    st(0), st(2)  ; stack: root*2/3, x/3/root^2, 2/3, x/3
    faddp                 ; stack: root*2/3 + x/3/root^2, 2/3, x/3

; TODO: compare and loop back to loop_body

    fstp    [root]         ; store and pop
    fstp    st(0)          ; pop the two constants off the FP stack to empty it before returning
    fstp    st(0)
    ; finit is very slow, ~80cycles, don't use it if you don't have to.

32位函数调用约定将FP结果返回到st(0),因此您可以这样做,但调用者可能必须存储在某处。

答案 1 :(得分:4)

我将在非常基本的层面为那些可能面临需要在FPU上进行计算的x87新手来回答这个问题。

有两件事需要考虑。如果给你一个计算(INFIX notation),如:

root := (2.0*root + x/(root*root)) / 3.0

有没有办法将其转换为x87 FPU可以使用的基本指令?是的,在一个非常基础的层面上,x87 FPU就像一个复杂的RPN计算器。代码中的等式是 INFIX 表示法。如果将其转换为 POSTFIX (RPN)表示法,则可以轻松地将其实现为具有操作的堆栈。

document提供了有关转换为 POSTFIX 表示法的一些信息。遵循规则,您的 POSTFIX 等效项将如下所示:

2.0 root * x root root * / + 3.0 /

您可以使用root=1x=27 {/ 1}}中的这些密钥将其放入旧的RPN计算器(HP),例如HP 15C

2.0 [enter] root * x [enter] root [enter] root * / + 3.0 /

在线HP 15C应显示该计算结果为9.667。将其翻译为基本x87:

  • 数字是向堆栈顶部推送(fld)
  • 变量是向堆栈顶部推送(fld)
  • *是fmulp(ST(0)乘以ST(1),将结果存储在ST(1)中,弹出寄存器堆栈)
  • /是fdivp(将ST(1)除以ST(0),将结果存储在ST(1)中,然后弹出寄存器堆栈)
  • +是faddp(将ST(0)添加到ST(1),将结果存储在ST(1)中,然后弹出寄存器堆栈)
  • -是fsubp(从ST(1)减去ST(0),将结果存储在ST(1),弹出寄存器堆栈)

您可以将2.0 root * x root root * / + 3.0 /字面转换为x87指令:

fld Two      ; st(0)=2.0
fld root     ; st(0)=root, st(1)=2.0
fmulp        ; st(0)=(2.0 * root)
fld xx       ; st(0)=x, st(1)=(2.0 * root)
fld root     ; st(0)=root, st(1)=x, st(2)=(2.0 * root)
fld root     ; st(0)=root, st(1)=root, st(2)=x, st(3)=(2.0 * root)
fmulp        ; st(0)=(root * root), st(1)=x, st(2)=(2.0 * root)
fdivp        ; st(0)=(x / (root * root)), st(1)=(2.0 * root)
faddp        ; st(0)=(2.0 * root) + (x / (root * root))
fld Three    ; st(0)=3.0, st(1)=(2.0 * root) + (x / (root * root))
fdivp        ; st(0)=((2.0 * root) + (x / (root * root))) / 3.0

一旦掌握了基础知识,就可以继续提高效率。

关于编辑2 /跟进问题

要记住的一件事是,如果您不使用从堆栈中弹出值的指令,则循环的每次迭代将消耗更多的FPU堆栈槽。通常,以 P 结尾的FPU指令会从堆栈中弹出值。你没有使用任何指令从堆栈中删除项目,FPU堆栈不断增长。

与用户空间中的程序堆栈不同,FPU堆栈非常有限,因为它只有8个插槽。如果在堆栈上放置8个以上的活动值,则会以1#IND的形式出现溢出错误。如果我们分析您的代码并在每条指令后查看堆栈,我们就会发现:

    fld     root            ; st(0)=root  
repreatAgain:
    fmul    two             ; st(0)=(2.0*root)      
    fld     root            ; st(0)=root, st(1)=(2.0*root) 
    fmul    st(0), st(0)    ; st(0)=(root*root), st(1)=(2.0*root)
    fld     xx              ; st(0)=x, st(1)=(root*root), st(2)=(2.0*root)
    fdiv    st(0), st(1)    ; st(0)=(x/(root*root)), st(1)=(root*root), st(2)=(2.0*root)
    fadd    st(0), st(2)    ; st(0)=((2.0*root) + x/(root*root)), st(1)=(root*root), st(2)=(2.0*root)
    fld     three           ; st(0)=3.0, st(1)=((2.0*root) + x/(root*root)), st(2)=(root*root), st(3)=(2.0*root)                                            
    fld     st(1)           ; st(0)=((2.0*root) + x/(root*root)), st(1)=3.0, st(2)=((2.0*root) + x/(root*root)), st(3)=(root*root), st(4)=(2.0*root)
    fdiv    st(0), st(1)    ; st(0)=(((2.0*root) + x/(root*root))/3.0), st(1)=3.0, st(2)=((2.0*root) + x/(root*root)), st(3)=(root*root), st(4)=(2.0*root)
    jmp     repreatAgain

观察到在最后一条 FDIV 指令之后和 JMP 之前,我们在堆栈上有5个项目( st(0) ST(4))。当我们进入循环时,我们在 st(0)中只有1 root。解决此问题的最佳方法是使用指令,以便在计算过程中从堆栈中弹出(删除)值。

另一种效率较低的方法是在重复循环之前释放我们不再需要的值。 FFREE指令可用于此目的,方法是从堆栈底部开始手动标记未使用的条目。如果您在上面的代码之后以及jmp repreatAgain代码应该工作之前添加这些行:

ffree   st(4)           ; st(0)=(((2.0*root) + x/(root*root))/3.0), st(1)=3.0, st(2)=((2.0*root) + x/(root*root)), st(3)=(root*root)
ffree   st(3)           ; st(0)=(((2.0*root) + x/(root*root))/3.0), st(1)=3.0, st(2)=((2.0*root) + x/(root*root))
ffree   st(2)           ; st(0)=(((2.0*root) + x/(root*root))/3.0), st(1)=3.0
ffree   st(1)           ; st(0)=(((2.0*root) + x/(root*root))/3.0)
fst     root            ; Update root variable
jmp     repreatAgain

使用 FFREE 指令,我们只用 st(0)中的新root结束循环。

由于您的计算方式,我还添加了fst root。您的计算包括fld root,它依赖于每个循环结束时root更新的值。有一种更有效的方法可以做到这一点,但是我提供了一个可以在当前代码中运行的修复,而不需要太多的工作。

如果您使用我之前提供的效率低下/简单的代码片段进行计算,您最终会得到以下代码:

    finit        ; initialize FPU
repreatAgain:
    fld Two      ; st(0)=2.0
    fld root     ; st(0)=root, st(1)=2.0
    fmulp        ; st(0)=(2.0 * root)
    fld xx       ; st(0)=x, st(1)=(2.0 * root)
    fld root     ; st(0)=root, st(1)=x, st(2)=(2.0 * root)
    fld root     ; st(0)=root, st(1)=root, st(2)=x, st(3)=(2.0 * root)
    fmulp        ; st(0)=(root * root), st(1)=x, st(2)=(2.0 * root)
    fdivp        ; st(0)=(x / (root * root)), st(1)=(2.0 * root)
    faddp        ; st(0)=(2.0 * root) + (x / (root * root))
    fld Three    ; st(0)=3.0, st(1)=(2.0 * root) + (x / (root * root))
    fdivp        ; newroot = st(0)=((2.0 * root) + (x / (root * root))) / 3.0
    fstp root    ; Store result at top of stack into root and pop value
                 ;     at this point the stack is clear again since
                 ;     all items pushed have been popped.

    jmp repreatAgain

此代码不需要 FFREE ,因为随着计算的进行,元素会从堆栈中弹出。指令 FADDP FSUBP FDIVP FADDP 将另外弹出堆栈顶部的值。这具有使堆栈不受部分中间计算影响的副作用。

集成循环

要将循环集成到我之前创建的简单/低效代码中,您可以使用FCOM (Floating point compare)的变体进行比较。然后将浮点比较的结果传送/转换为常规CPU标志(EFLAGS)。然后可以使用常规比较运算符来执行条件检查。代码可能如下所示:

epsilon REAL4   0.001

.CODE
main PROC
    finit              ; initialize FPU

repeatAgain:
    fld Two            ; st(0)=2.0
    fld root           ; st(0)=root, st(1)=2.0
    fmulp              ; st(0)=(2.0 * root)
    fld xx             ; st(0)=x, st(1)=(2.0 * root)
    fld root           ; st(0)=root, st(1)=x, st(2)=(2.0 * root)
    fld root           ; st(0)=root, st(1)=root, st(2)=x, st(3)=(2.0 * root)
    fmulp              ; st(0)=(root * root), st(1)=x, st(2)=(2.0 * root)
    fdivp              ; st(0)=(x / (root * root)), st(1)=(2.0 * root)
    faddp              ; st(0)=(2.0 * root) + (x / (root * root))
    fld Three          ; st(0)=3.0, st(1)=(2.0 * root) + (x / (root * root))
    fdivp              ; newroot=st(0)=((2.0 * root) + (x / (root * root))) / 3.0
    fld root           ; st(0)=oldroot, st(1)=newroot
    fsub st(0), st(1)  ; st(0)=(oldroot-newroot), st(1)=newroot
    fabs               ; st(0)=(|oldroot-newroot|), st(1)=newroot
    fld epsilon        ; st(0)=0.001, st(1)=(|oldroot-newroot|), st(2)=newroot
    fcompp             ; Do compare&set x87 flags pop top two values off stack
                       ;     st(0)=newroot    
    fstsw ax           ; Copy x87 Status Word containing the result to AX
    fwait              ; Insure previous instruction completed
    sahf               ; Transfer condition codes to the CPU's flags register

    fstp root          ; Store result (newroot) at top of stack into root 
                       ;     and pop value. At this point the stack is clear
                       ;     again since all items pushed have been popped.
    jbe repeatAgain    ; If 0.001 <= (|oldroot-newroot|) repeat
    mov eax, 0         ; exit
    ret
main    ENDP 
END

注意: FCOMPP 的使用以及x87标志到CPU标志的手动传输是由于代码顶部有 .586 指令这一事实所致。我假设因为您没有指定 .686 或更晚,而FCOMI之类的指令不可用。如果您使用.686或更高版本,则代码的底部部分可能如下所示:

fld root           ; st(0)=oldroot, st(1)=newroot
fsub st(0), st(1)  ; st(0)=(oldroot-newroot), st(1)=newroot
fabs               ; st(0)=(|oldroot-newroot|), st(1)=newroot
fld epsilon        ; st(0)=0.001, st(1)=(|oldroot-newroot|), st(2)=newroot
fcomip st(0),st(1) ; Do compare & set CPU flags, pop one value off stack
                   ;     st(0)=(|oldroot-newroot|), st(1)=newroot
fstp st(0)         ; Pop temporary value off top of stack
                   ;     st(0)=newroot

fstp root          ; Store result (newroot) at top of stack into root 
                   ;     and pop value. At this point the stack is clear
                   ;     again since all items pushed have been popped.
jbe repeatAgain    ; If 0.001 <= (|oldroot-newroot|) repeat

从Infix表示法创建RPN / Postfix的快速方法

如果学习将 Infix 符号转换为 RPN / Postfix ,那么我在之前在我的问题中链接的文档似乎有点令人生畏,有一些缓解。有很多网站会为您完成这项工作。其中一个网站是MathBlog。只需输入您的等式,单击转换,它应显示 RPN / Postfix 等效项。它仅限于+ - / *,括号和带有^。

的指数

优化

优化代码的一个关键是通过将每个循环之间保持不变的部分与可变部分分开来优化公式。可以在循环开始之前计算常量部分。

你原来的等式是:

Original Equation

分离我们可以得到的常数部分:

Separate Constants

如果我们用twothirds = 2.0 / 3.0和xover3 = x / 3的标识符替换常量,那么我们最终会得到一个如下所示的简化公式:

Final equation

如果我们将其转换为 POSTFIX / RPN ,那么我们得到:

twothirds root * xover3 root root * / +

Peter在 Better loop body 部分的答案中正在利用类似的优化。他将常量TwothirdsXover3放在循环外的x87 FPU堆栈上,并在循环内根据需要引用它们。这样可以避免每次通过循环时不必要地从内存中重新读取它们。

基于上述优化的更完整示例:

.586
.MODEL FLAT
.STACK 4096

.DATA
xx        REAL4   27.0
root      REAL4   1.0
Three     REAL4   3.0
epsilon   REAL4   0.001
Twothirds REAL4 0.6666666666666666

.CODE
main PROC
    finit               ; Initialize FPU
    fld epsilon         ; st(0)=epsilon
    fld root            ; st(0)=prevroot (Copy of root), st(1)=epsilon
    fld TwoThirds       ; st(0)=(2/3), st(1)=prevroot, st(2)=epsilon 
    fld xx              ; st(0)=x, st(1)=(2/3), st(2)=prevroot, st(3)=epsilon
    fdiv Three          ; st(0)=(x/3), st(1)=(2/3), st(2)=prevroot, st(3)=epsilon
    fld st(2)           ; st(0)=root, st(1)=(x/3), st(2)=(2/3), st(3)=prevroot, st(4)=epsilon

repeatAgain:

    ; twothirds root * xover3 root root * / +
    fld st(0)           ; st(0)=root, st(1)=root, st(2)=(x/3), st(3)=(2/3), st(4)=prevroot, st(5)=epsilon
    fmul st(0), st(3)   ; st(0)=(2/3*root), st(1)=root, st(2)=(x/3), st(3)=(2/3), st(4)=prevroot, st(5)=epsilon           
    fxch                ; st(0)=root, st(1)=(2/3*root), st(2)=(x/3), st(3)=(2/3), st(4)=prevroot, st(5)=epsilon
    fmul st(0), st(0)   ; st(0)=(root*root), st(1)=(2/3*root), st(2)=(x/3), st(3)=(2/3), st(4)=prevroot, st(5)=epsilon
    fdivr st(0), st(2)  ; st(0)=((x/3)/(root*root)), st(1)=(2/3*root), st(2)=(x/3), st(3)=(2/3), st(4)=prevroot, st(5)=epsilon
    faddp               ; st(0)=((2/3*root)+(x/3)/(root*root)), st(1)=(x/3), st(2)=(2/3), st(3)=prevroot, st(4)=epsilon
    fxch st(3)          ; st(0)=prevroot, st(1)=(x/3), st(2)=(2/3), newroot=st(3)=((2/3*root)+(x/3)/(root*root)), st(4)=epsilon 
    fsub st(0), st(3)   ; st(0)=(prevroot-newroot), st(1)=(x/3), st(2)=(2/3), st(3)=newroot, st(4)=epsilon
    fabs                ; st(0)=(|prevroot-newroot|), st(1)=(x/3), st(2)=(2/3), st(3)=newroot, st(4)=epsilon
    fld st(4)           ; st(0)=0.001, st(1)=(|prevroot-newroot|), st(2)=(x/3), st(3)=(2/3), st(4)=newroot, st(5)=epsilon

    fcompp              ; Do compare&set x87 flags pop top two values off stack
                        ;     st(0)=(x/3), st(1)=(2/3), st(2)=newroot, st(3)=epsilon    
    fstsw ax            ; Copy x87 Status Word containing the result to AX
    fwait               ; Insure previous instruction completed
    sahf                ; Transfer condition codes to the CPU's flags register

    fld st(2)           ; st(0)=newroot, st(1)=(x/3), st(2)=(2/3), st(3)=newroot, st(4)=epsilon
    jbe repeatAgain     ; If 0.001 <= (|oldroot-newroot|) repeat

    ; Remove temporary values on stack, cubed root in st(0)
    ffree st(4)         ; st(0)=newroot, st(1)=(x/3), st(2)=(2/3), st(3)=epsilon
    ffree st(3)         ; st(0)=newroot, st(1)=(x/3), st(2)=(2/3)
    ffree st(2)         ; st(0)=newroot, st(1)=(x/3)
    ffree st(1)         ; st(0)=newroot

    mov     eax, 0  ; exit
    ret
main ENDP 

END

此代码在进入循环之前将这些值放在堆栈上:

  • st(4)= Epsilon值(0.001)
  • st(3)=计算完成前root的副本(有效prevroot
  • st(2)=常数Twothirds(2/3)
  • st(1)= Xover3(x / 3)
  • st(0)= root
  • 的有效副本

在循环重复之前,堆栈将具有上面的布局。

退出前结束时的代码将删除所有临时值,并在 st(0)的顶部将堆栈中的值root放置。