一百個例題 (21~24)
Ching-Tang Tseng
Hamilton, New Zealand
26 August 2024
第(21)個範例是能夠叫用自己的自用副程式功能展示。
前面求行列式值的範例曾經說過, Forth 天生具有叫用自己的能力,使用時的指令名稱就叫做 RECURSE 。它的工作原理也很簡單,就是在現行編譯位址內填入現正編譯指令的執行位址,就能達到目的了。如果用 Fig-Forth 的標準來解釋,最為簡單:
: RECURSE LATEST , ; IMMEDIATE
但是,這樣叫用自己的方式,與其他傳統程式語言的自用副程式執行方式,有著很大的不同。 Forth 不處理參數的傳遞,別種程式語言則在系統內耗用大量的記憶體,來進行大量參數如矩陣類的傳遞。因 Forth 只留單一個自己的起始執行位址來達到叫用自己的目的,若還要包括許多參數,就得另行設法。使用自用副程式設計程式時,外觀上的層次性影響在認知方面有點複雜,並不很受歡迎,我也不喜歡使用。所以,這個程式只是為了展示功能與用法,才將其列入範例,僅供留參。
這個範例中有三個例題,都是以叫用自己的方式設計的。第一個是求階乘的函數, 32 位元的整數系統,能表現的最大正整數,大約只能算到 12 的階乘。
使用 RECURSE 時最需要牢記的要點,就是程式中必定被分成兩個部份來處理,一個部份就是不能每次都被執行的事情,另個部份就是每次都得執行的部份。求階乘時, 0! 恆等於 1 ,是特例,就是不能每次都被執行的部份。而每次都得執行的部份又有另一個特點,就是 RECURSE 不能無限次的執行下去,否則當機。因此,程式中必定有個必須停止再度叫用自己的邏輯判斷機制,讓執行有機會能夠停止下來。這三個範例中,我都在程式後面加註了註解,仔細閱讀就能明白。
第二個例題是搬運 Hanoi 三柱塔上,只能小片壓大片之碟片搬運規矩的展示程式,這原本是一個早期討論人工智慧時,以專家系統的立場來解題的範例。程式內有個特殊設計,用到了局部變數(local variables)的技巧,所以,我刻意將其譯寫成 BASIC 的格式,考驗一下我的 ABC Forth 系統內,能不能也具有使用局部變數的功能?程式證明了,可以!所以,十二年前,我曾將這種具有局部變數與叫用自己的自用副程式能力之雙重特殊性能的 BASIC 系統,公開在國際論壇上展示貼文,引起許多人的注意。大家可以仔細看看這兩項同時存在的功能,不是其他一般 BASIC 所能具有的性能。
第三個例題是將 1 至指定數字之間,所有間隔為 5 的數字,以一五、一十的方式,全部印出來的範例。問題簡單,很容易懂,但在邊界處,要添加將 i0 指定為 0 的工作,才能正確執行,這是我們在設計程式時,經常會碰到的起始值設定問題,所以,我也將其留參。
我再強調一次,叫用自己的自用副程式功能,不是個程式語言中絕對需要的性能,我很少使用。大家喜歡強調,我就能以具體事實回應,我的系統也行,如此而已。 :
前面求行列式值的範例曾經說過, Forth 天生具有叫用自己的能力,使用時的指令名稱就叫做 RECURSE 。它的工作原理也很簡單,就是在現行編譯位址內填入現正編譯指令的執行位址,就能達到目的了。如果用 Fig-Forth 的標準來解釋,最為簡單:
: RECURSE LATEST , ; IMMEDIATE
但是,這樣叫用自己的方式,與其他傳統程式語言的自用副程式執行方式,有著很大的不同。 Forth 不處理參數的傳遞,別種程式語言則在系統內耗用大量的記憶體,來進行大量參數如矩陣類的傳遞。因 Forth 只留單一個自己的起始執行位址來達到叫用自己的目的,若還要包括許多參數,就得另行設法。使用自用副程式設計程式時,外觀上的層次性影響在認知方面有點複雜,並不很受歡迎,我也不喜歡使用。所以,這個程式只是為了展示功能與用法,才將其列入範例,僅供留參。
這個範例中有三個例題,都是以叫用自己的方式設計的。第一個是求階乘的函數, 32 位元的整數系統,能表現的最大正整數,大約只能算到 12 的階乘。
使用 RECURSE 時最需要牢記的要點,就是程式中必定被分成兩個部份來處理,一個部份就是不能每次都被執行的事情,另個部份就是每次都得執行的部份。求階乘時, 0! 恆等於 1 ,是特例,就是不能每次都被執行的部份。而每次都得執行的部份又有另一個特點,就是 RECURSE 不能無限次的執行下去,否則當機。因此,程式中必定有個必須停止再度叫用自己的邏輯判斷機制,讓執行有機會能夠停止下來。這三個範例中,我都在程式後面加註了註解,仔細閱讀就能明白。
第二個例題是搬運 Hanoi 三柱塔上,只能小片壓大片之碟片搬運規矩的展示程式,這原本是一個早期討論人工智慧時,以專家系統的立場來解題的範例。程式內有個特殊設計,用到了局部變數(local variables)的技巧,所以,我刻意將其譯寫成 BASIC 的格式,考驗一下我的 ABC Forth 系統內,能不能也具有使用局部變數的功能?程式證明了,可以!所以,十二年前,我曾將這種具有局部變數與叫用自己的自用副程式能力之雙重特殊性能的 BASIC 系統,公開在國際論壇上展示貼文,引起許多人的注意。大家可以仔細看看這兩項同時存在的功能,不是其他一般 BASIC 所能具有的性能。
第三個例題是將 1 至指定數字之間,所有間隔為 5 的數字,以一五、一十的方式,全部印出來的範例。問題簡單,很容易懂,但在邊界處,要添加將 i0 指定為 0 的工作,才能正確執行,這是我們在設計程式時,經常會碰到的起始值設定問題,所以,我也將其留參。
我再強調一次,叫用自己的自用副程式功能,不是個程式語言中絕對需要的性能,我很少使用。大家喜歡強調,我就能以具體事實回應,我的系統也行,如此而已。 :
\ Recursive in BASIC \ 能執行自用副程式之BASIC性能展示 \ 注意!這只是一個在BASIC環境中強行使用recurse的示範程式。 \ 有效計算範圍只在12以下有效,13以上都是錯的。 2 integers u u! : (u!) ( -- ) BASIC 10 if u = 1 then 50 \ 結束叫用自己時,所需要的終止條件 20 let u! = u! * u 30 let u = u - 1 40 run recurse \ 以上為叫用自己時,每次都必須執行的內容 50 end ; : BFact ( u -- u! ) [[ u ]] ! BASIC 10 let u! = 1 20 if u = 0 then 40 \ 以上為不能在每次叫用自己時都執行的內容 30 run (u!) 40 end u! ; : Ffact ( u -- u! ) DUP 0= if DROP 1 else DUP 1- recurse * then ; \s \ ********************************************************** \ 原 hanoi 程式將左邊(left)的疊片搬到中間(middle),BASIC則改為left搬到right \ 程式的特色在展示局部變數與叫用自用副程式的功能均可在BASIC式程式中使用 CREATE peg1 ." left " CREATE peg2 ." right " \ peg2換成right CREATE peg3 ." middle " \ peg3換成middle : .string ( addr -- ) COUNT TYPE ; : (hanoi) ( n peg1 peg2 peg3 -- ) LOCALS| vvia tto ffrom n | \ 局部變數 BASIC 10 if n = 1 then 30 \ 結束叫用自用副程式的條件在此 20 goto 50 30 run cr ." Move disk from " ffrom .string ." to " tto .string 40 goto 100 50 run n 1- ffrom vvia tto recurse \ 可以多次使用叫用自用副程式 => 1 ffrom tto vvia recurse => n 1- vvia tto ffrom recurse 100 end ; : hanoi ( n -- ) peg1 peg2 peg3 (hanoi) ; (( \ 原始參考程式 CREATE peg1 ," left " CREATE peg2 ," middle " CREATE peg3 ," right " : .string ( addr -- ) COUNT TYPE ; : MOVE-DISK ( n peg1 peg2 peg3 -- ) LOCALS| vvia tto ffrom n | n 1 = IF CR ." Move disk from " ffrom .string ." to " tto .string ELSE n 1- ffrom vvia tto RECURSE 1 ffrom tto vvia RECURSE n 1- vvia tto ffrom RECURSE THEN ; : test ( n -- ) peg1 peg2 peg3 MOVE-DISK ; )) \ ********************************************************* 2 integers i0 i9 : (ex) ( -- ) BASIC 10 if i0 > i9 then 100 \ 結束recurse的條件 20 let i0 = i0 + 5 30 run i0 . 40 run recurse \ 以上為recurse的部份 100 let i0 = 0 \ 歸零才可以供下一次繼續使用 110 end ; : ex ( n -- ) [[ i9 ]] ! \ 不可以recurse的部份 BASIC 10 run (ex) 20 end ; cr cr .( Usage: ) cr .( u Bfact u. ) cr .( u Ffact u. ) cr .( n hanoi ) cr .( n ex ) cr \ .( n peg1 peg2 peg3 MOVE-DISK ) cr
第(22)個範例是修正 Win32Forth 系統中之除法運算的程式。
最近所用的範例,都有其精彩的創作性,不要小看這些材料。
如果您知道開源式作業系統,能夠一口氣就公開提供將近 400 多個可自由叫用的功能程式時,您就不會認為第(20)個程式是個沒什麼內容的東西了。
如果您知道 Forth 與生俱來就存在有自用副程式功能,如第(21)個範例所示,您也就不會認為別人能執行得出來的程式功能 Forth 會辦不到了。
同樣的,今天要討論的範例,是一個算法上的原則問題,當您發現這麼大的一個系統,連最基本的整數除法運算都與眾不同時,怎麼辦?這個問題非常根本,我拿 Forth 研究數學計算,跟全世界的 Forth 流行趨勢不一樣,要搞得出名堂,就得深究問題,才能創作得出好的結果。
數學是一門非常嚴謹的科學,不能接受我在範例程式中列舉的運算規則,整數除法以 -15 除以 4 得到近似的 -4 而不是正確的 -3 。這個問題,是我在自行創作分數計算系統進行測試時發現的,但它並不是一個錯誤的設計,原創者有他硬要這樣設計的理由,我不想進一步說明。現在,我只強調這與數學運算基本要求不符,必須修正,因為我專注於發展數學計算應用,別種應用,我沒興趣。
發現問題之初,我僅採用高階定義方式解決問題,所以程式中用到了一個 ANSI 標準的 SM/REM 指令,它強調除完後的餘數必須與被除數同號,就能用來協助我解決問題。(相對的,另外有一個叫做 FM/REM 的指令,它強調除完後餘數必須與除數同號)。
待我完成整個分數系統的創作與測試後,我自覺以高階方式設計的指令,執行速度必定較慢,於是,再自行詳讀組合語言的說明書,將 /MOD 及 / 兩個指令,都修正成符合數學運算基本要求的低階組合語言程式了。為了避免與系統原始設計指令用名衝突,我改用 asm 前引於這兩個指令名稱之前,當作新的指令名稱,並固定於新長成的系統中。所以,一旦您再度載入這個程式時,您會發現系統告訴您指令用名重複了,但不影響後續運作。我在自創的分數運算系統中,確實使用了這個範例中的低階組合語言,設計這兩個指令。
這個範例是一個很好的 Forth 組合語言應用範例,我在網上見過名家介紹全世界組合語言工具時,竟然發現,也把 Win32Forth 中的組合語言功能,當作一個項目來表彰。但請注意!雖然這個系統中的組合語言功能,是作者 Jim Schnieder 於 1995 年捐贈出來的程式,必須注意!它有版權。公開發表用到它時的創作,就必須道謝,不能亂用它來營利,它只是一個公益捐獻。我若公開貼文用到了它,也必定會照規矩做出宣告。
搞 Forth 的人,為了解決所有的問題,免不了絕對必須接觸低階的組合語言,這個範例也在強調這種精神。
如果我對某個 Forth 系統有興趣,我就必定會深入使用到其最根本的運行工具:組合語言。任何 Forth 系統都能跑組合語言,有的時候,還得從源程式起,就強行使用組合語言,才能把系統搞得十全十美。我在接觸 Lina Forth 後,更有這種感覺。如果您沒有能力在 Forth 系統中使用組合語言來設計程式,那就必定會有許多問題無法解決。
這個範例教大家一丁點 Forth 的組合語言,實際上是告訴大家,必須熟悉組合語言,否則搞不出 ABC Forth 系統。因為系統裡面,有許多地方都必須使用組合語言來解決問題。 Forth 本身就具有組合語言的使用能力,就看您怎麼用,這個範例就是一種標準用法,是一個很好的範例。
也許大家都很想學數學計算系統整套的設計方法,但我只談到了 20 幾個範例程式,就已經涉及了許多額外話題,數學上的算法學、系統運行的哲理、系統的性能、設計的技巧、理論問題 …. 等等題外話。這百個範例,後面幾乎都是我的個人創作,要不然,就是我改寫後的成果,改寫程式的技術,並不是件簡單事情。我並沒有把大型程式列入百例,因為太大了,就無法以短文討論,只能一天一頁。想更深入搞清問題者,必須自己執行程式,得出結果,才能全面了解。 :
最近所用的範例,都有其精彩的創作性,不要小看這些材料。
如果您知道開源式作業系統,能夠一口氣就公開提供將近 400 多個可自由叫用的功能程式時,您就不會認為第(20)個程式是個沒什麼內容的東西了。
如果您知道 Forth 與生俱來就存在有自用副程式功能,如第(21)個範例所示,您也就不會認為別人能執行得出來的程式功能 Forth 會辦不到了。
同樣的,今天要討論的範例,是一個算法上的原則問題,當您發現這麼大的一個系統,連最基本的整數除法運算都與眾不同時,怎麼辦?這個問題非常根本,我拿 Forth 研究數學計算,跟全世界的 Forth 流行趨勢不一樣,要搞得出名堂,就得深究問題,才能創作得出好的結果。
數學是一門非常嚴謹的科學,不能接受我在範例程式中列舉的運算規則,整數除法以 -15 除以 4 得到近似的 -4 而不是正確的 -3 。這個問題,是我在自行創作分數計算系統進行測試時發現的,但它並不是一個錯誤的設計,原創者有他硬要這樣設計的理由,我不想進一步說明。現在,我只強調這與數學運算基本要求不符,必須修正,因為我專注於發展數學計算應用,別種應用,我沒興趣。
發現問題之初,我僅採用高階定義方式解決問題,所以程式中用到了一個 ANSI 標準的 SM/REM 指令,它強調除完後的餘數必須與被除數同號,就能用來協助我解決問題。(相對的,另外有一個叫做 FM/REM 的指令,它強調除完後餘數必須與除數同號)。
待我完成整個分數系統的創作與測試後,我自覺以高階方式設計的指令,執行速度必定較慢,於是,再自行詳讀組合語言的說明書,將 /MOD 及 / 兩個指令,都修正成符合數學運算基本要求的低階組合語言程式了。為了避免與系統原始設計指令用名衝突,我改用 asm 前引於這兩個指令名稱之前,當作新的指令名稱,並固定於新長成的系統中。所以,一旦您再度載入這個程式時,您會發現系統告訴您指令用名重複了,但不影響後續運作。我在自創的分數運算系統中,確實使用了這個範例中的低階組合語言,設計這兩個指令。
這個範例是一個很好的 Forth 組合語言應用範例,我在網上見過名家介紹全世界組合語言工具時,竟然發現,也把 Win32Forth 中的組合語言功能,當作一個項目來表彰。但請注意!雖然這個系統中的組合語言功能,是作者 Jim Schnieder 於 1995 年捐贈出來的程式,必須注意!它有版權。公開發表用到它時的創作,就必須道謝,不能亂用它來營利,它只是一個公益捐獻。我若公開貼文用到了它,也必定會照規矩做出宣告。
搞 Forth 的人,為了解決所有的問題,免不了絕對必須接觸低階的組合語言,這個範例也在強調這種精神。
如果我對某個 Forth 系統有興趣,我就必定會深入使用到其最根本的運行工具:組合語言。任何 Forth 系統都能跑組合語言,有的時候,還得從源程式起,就強行使用組合語言,才能把系統搞得十全十美。我在接觸 Lina Forth 後,更有這種感覺。如果您沒有能力在 Forth 系統中使用組合語言來設計程式,那就必定會有許多問題無法解決。
這個範例教大家一丁點 Forth 的組合語言,實際上是告訴大家,必須熟悉組合語言,否則搞不出 ABC Forth 系統。因為系統裡面,有許多地方都必須使用組合語言來解決問題。 Forth 本身就具有組合語言的使用能力,就看您怎麼用,這個範例就是一種標準用法,是一個很好的範例。
也許大家都很想學數學計算系統整套的設計方法,但我只談到了 20 幾個範例程式,就已經涉及了許多額外話題,數學上的算法學、系統運行的哲理、系統的性能、設計的技巧、理論問題 …. 等等題外話。這百個範例,後面幾乎都是我的個人創作,要不然,就是我改寫後的成果,改寫程式的技術,並不是件簡單事情。我並沒有把大型程式列入百例,因為太大了,就無法以短文討論,只能一天一頁。想更深入搞清問題者,必須自己執行程式,得出結果,才能全面了解。 :
\ (22)Asmdiv.f \ 20140313 code asm/MOD ( n1 n2 -- r q ) mov ecx, edx pop eax cdq idiv ebx push edx mov ebx, eax mov edx, ecx next c; \ : asmMOD ( n1 n2 -- r ) asm/MOD DROP ; \ : asm/ ( n1 n2 -- q ) asm/MOD NIP ; code asm/ ( n1 n2 -- q ) mov ecx, edx \ save UP pop eax \ 被除數 cdq idiv ebx \ 除數 mov ebx, eax \ 商 mov edx, ecx \ restore UP next c; \S Win32Forth原系統中的除法指令( / ),執行結果與數學計算之基本規則不符。 例如:執行 -15 4 / . 時會得到不合理的 -4,合理結果應該是 -3。 大部份Forth系統及C語言系統,均輸出-3而非-4,因此,必須重新設計/及/MOD。 臨時以高階定義方式完成之/指令,變通性設計如下: : / ( n1 n2 -- q ) >r s>d r> sm/rem nip ; 參考VFX Forth系統及Win32Forth系統自身內的SM/REM內容便可以仿照設計,資料如下: in VFX forth see / code / ( n1 n2 -- n3 ) mov eax, [ebp] cdq idiv ebx mov ebx, eax lea ebp, [ebp+04] next, 其中: CDQ 指令為有號數除法專用指令,執行時會將EAX暫存器的最高位元值填滿整個EDX EAX ==> EDX:EAX IDIV 來源運算元 指令用於有號數除法運算。上例為 IDIV EBX。 EBX放的是除數,被除數則放在EDX:EAX。 執行後,商放在EAX,餘數放在EDX。 in Win32Forth SEE SM/REM SM/REM IS CODE ( $401C28 8BCA ) mov ecx, edx ( $401C2A 5A ) pop edx ( $401C2B 58 ) pop eax ( $401C2C F7FB ) idiv ebx ( $401C2E 52 ) push edx ( $401C2F 8BD8 ) mov ebx, eax ( $401C31 8BD1 ) mov edx, ecx 測試結果: -15 -4 asm/ . 3 ok -15 4 asm/ . -3 ok 15 4 asm/ . 3 ok 15 -4 asm/ . -3 ok
沒有留言:
張貼留言