第(14)個程式用來算出 Fibonacci 級數的數字。
Fibonacci 級數的口語定義為:下一個數字,等於前兩個的和。它的意義、起源與用途,請自己查找網上資料。
範例程式中, Fib1 的程式還可以讓您看到一個加號計算,然後印出所有小於 50000 的一序列級數。不熟 Forth ,就很難感受出下一個是前兩個之和的計算是怎麼安排的?數學問題,採用純粹傳統 Forth 的語法寫程式時,因為只能用堆疊傳遞數字,必然影響這種直覺。
Fib2 根據前引輸入的數字,把所有低於此指定數字的 Fibonacci 數字印出來。程式中更看不出那裡才在做加法計算了,計算是隱性的,在執行 +loop 之後,系統會為迴路指標 I 執行加法,加出來的新指標量,會改放到回返堆疊去,不放在數據堆疊上。印出的數字,是 I 指標從回返堆疊取得的下標數字,這裡玩弄了一點程式技巧。
:
\ Example 14. The Fibonacci Sequence
: Fib1 ( -- , print all Fibonacci numbers less than 50000 )
1 1 ( two initial Fib numbers )
BEGIN OVER U. ( print the smaller number )
SWAP OVER + ( compute next Fib number )
DUP 50000 U> ( exit if number too large )
UNTIL ( else repeat )
2DROP ( discard the numbers )
;
: Fib2 ( n -- , display all Fibonacci numbers smaller than n )
1 ( initial number )
SWAP 1 ( set up range )
DO DUP U. ( print current number )
I ( the next Fibonacci number )
SWAP ( prepare the next to come )
+LOOP ( add current to index, if the )
( repeat until sum>n )
U. ( print the last Fib )
;
\s
Usage:
To test the routines, try:
Fib1
10000 Fib2
我只需發幾分鐘時間,就能根據口語的定義,寫出能夠執行出同樣結果的 ABC FORTH 程式, Fibonacci 的定義式子就表現在列號為 60 的程式中。比較程式的寫法後,就能感受得出我為什麼要創作 ABC FORTH 來寫數學計算程式了。
:
5 integers i n f(n) f(n+1) f(n+2)
: Setn ( -- )
basic
10 print " n = "
20 inputi n
30 end ;
: main
basic
10 run Setn cr
20 let f(n) = 1
30 run f(n) .
40 let f(n+1) = 1
50 run f(n+1) .
60 let f(n+2) = f(n) + f(n+1)
70 if f(n+2) > n then 1000
80 run f(n+2) .
90 let f(n) = f(n+1)
100 let f(n+1) = f(n+2)
110 goto -60
1000 end
;
取材自 16 位元環境中的範例,只把上限數字算到 65535 以下,最多只有 24 個。同樣的程式,在 32 位元環境中可以執行到大約第 46 個 Fibonacci 數字,可為十八億以上。在 64 位元系統中可以得到 91 個,使用者可以自己測試一下以體會可用數字的範圍。
更甚的是,我提供給大家的系統,具有大數計算能力,就拿這個 Fibonacci 級數舉例,它能夠算出第一萬個 Fibonacci 數字是多少? 可用規模大約有四萬七千多個,遠大於 91 個。
程式受輸出位數最多印出 10008 位數的限制,所以下列程式的使用上限之 N 才會大約是四萬七千多個。
程式及執行結果如下:
:
\ Fibonacci numbers
2 INTEGERS I N
0 BIGVARIABLE F(N) 10000 ALLOT
0 BIGVARIABLE F(N+1) 10000 ALLOT
0 BIGVARIABLE F(N+2) 10000 ALLOT
: SetN ( -- )
BASIC
10 PRINT " Enter n =: "
20 INPUTI N
30 END ;
: MAIN ( -- )
BASIC
10 RUN SetN
20 LET B{ F(N) = I>BIG ( 1 ) }B
30 LET B{ F(N+1) = I>BIG ( 1 ) }B
40 IF N < 3 THEN 100
50 FOR I = 1 TO N
60 LET B{ F(N+2) = F(N) + F(N+1) }B
70 LET B{ F(N) = F(N+1) }B
80 LET B{ F(N+1) = F(N+2) }B
90 NEXT I
100 PRINT " Fibonacci number( " ; N ; " )="
110 RUN CR F(N) BIG.
120 END ;
cr cr
.( Usage: main ) cr
main
Enter n =:
? 10000
Fibonacci number( 10000 )=
2090 digits
54438373113565281338734260993750380135389184554695 :50
96702624771584120858286562234901708305154793896054 :100
11738226759780263173843595847511162414391747026429 :150
59169925586334117906063048089793531476108466259072 :200
75936789915067796008830659796664196582493772180038 :250
14411588410424809979846964873753371800281637633177 :300
81927941101369262750979509800713596718023814710669 :350
91264421477525447858767456896380800296226513311135 :400
99297627266794414001015758000435107774659358053625 :450
02461707918059226414679005690752321895868142367849 :500
59388075642348375438634263963597073375626009896246 :550
26687461120417398194048750624437098686543156268471 :600
86195620146126642232711815040367018825205314845875 :650
81719353352982783780035190252923951783668946766191 :700
79538847124410284639354494846144507787625295209618 :750
87597272889220768537396475869543159172434537193611 :800
26374392633731300589616724805173798630636811500308 :850
83967495871026195246313524474995052041983051871683 :900
21623283859794627245919771454628218399695789223798 :950
91219943177546970521613108109655995063829726125384 :1000
82420078971090547540284381496119304650618661701229 :1050
83288964352733750792786069444761853525144421077928 :1100
04597990456129812942380915605503303233891960916223 :1150
66987599227829231918966880177185755555209946533201 :1200
28446502371153715141749290913104897203455577507196 :1250
64542523286202201950609148358522388271101670843305 :1300
11699421157751512555102516559318881640483441295570 :1350
38825477521111577395780115868397072602565614824956 :1400
46053870028033131186148539980539703155572752969339 :1450
95860798503815814462764338588285295358034248508454 :1500
26446471681531001533180479567436396815653326152509 :1550
57112748041192819602214884914828438912417852017450 :1600
73055389287178579235094177433833315068982393544219 :1650
88805429332440371194867215543576548565499134519271 :1700
09891980266518456492782782721295764924023550759555 :1750
82056475693653948733176590002063731265706435097094 :1800
82649710038733517477713403319028105575667931789470 :1850
02411880309460403436295347199746139227479154973035 :1900
64126330742308240519999961015497846673404583268529 :1950
60388301120765629245998136251652347093963049734046 :2000
44510636530416363082366924225776146828846179184322 :2050
4793434406079917883360676846711185597501
=========1=========2=========3=========4=========5
12345678901234567890123456789012345678901234567890
=========(c) 2018 Copyright, Bottom Ruler=========
OK
這麼大的 Fibonacci 數字有什麼用?
有人用它來產生亂數。理由是:請看顯示出來的數字結果,幾乎完全沒有規則可言,夠亂,隨便取一段或一截夠用的數字,都不會與別處取得的相同。如果光靠範例程式所能展示的性能,看不出這樣的結果。這也是我創作這套 ABC FORTH 的主因。
:
第(15)個程式是用來印出美國資訊交換標準碼(ASCII code)的範例程式。
它有什麼用?回顧第(2)個範例,我曾把大寫的 Y 碼修改成小寫的 y 碼後,程式才能正常執行出所有的結果。這就是它的典型應用例子,我曾用這個程式來印出 ASCII code ,進行比對,然後才仔細研究出原始程式的問題所在。
Forth 強調能夠執行即時控制(real time control)。單板電腦上裝 Forth ,開機啟動後,就能直接下命令執行對外控制。個人電腦上裝的 Forth ,也是啟動 Forth 後就能對事物進行控制。沒加裝特殊硬體時怎麼控制?就用鍵盤。所以會有 KEY , ?KEY 等指令可供即時控制需要之時使用。
鍵盤的存在,自古以來就很多變,但有個慣例不變,就是大家相約成俗的只使用 ASCII code 編碼。一般而言,初期發展 Forth 系統時,系統對大小寫嚴格區分。後來,大家覺得使用起來很不方便,於是,就將系統設計成大小寫不分,系統接受輸入後,就自行將大小寫的輸入都轉換成大寫的碼來處理。 Win32Forth , Lina64 都是,但只對執行比較、找到指令時的工作內容是這樣,做其他事情時,則還是保持對鍵盤輸入大小寫不同時,字碼嚴格區分。
至於後來出現文字編碼有所謂 BIG-5 或 UTF-8 編碼稱呼上的不同,放心,那只是用在電腦螢幕輸出顯示字型的編碼而已,對現行 Forth 系統的運作,不會有很大的影響,也就是鍵盤碼跟顯示字型,沒有直接的關係。如果您寫程式時,會在檔案中加註中文說明,那在轉換作業系統時,就會出問題。檔案的顯示碼必須轉換,才能看得到正確的文字顯示。克服這種問題的方法也不麻煩,只需一列作業系統操作指令就能解決,這裡就不討論。
這個範例程式設計成能將 ASCII code 對應碼印成表格,一個橫印,另個直印,鍵盤的控制碼與特殊鍵的碼,則不印出來。以後,必須學會如何以操作方式,獲得這些鍵的碼。以前,在國際網壇上,曾有貼文詢問如何獲得滑鼠鍵碼的問題,以便設計出能夠控制顯示 Google 地圖的應用程式,很久都沒有人回答,每當憶及這些苦況,心中常有不忍。後來,我在『行星齒輪模擬程式』那篇文章中就故意披露方法,大多數人可能不太會去注意。
一個長期使用 Forth 的人,會在電腦旁準備幾樣東西,令其垂手可得、利於參考。以我為例,我準備有 : F79 , F83 與 ANSI 標準指令卡、 F79 與後來標準不同指令的對照表、能快速查得十進制與十六進制的 ASCII code 表、我自己創作之系統的使用說明.....等等等,這些東西,都是設計程式時的必備工具,經常要用。
還有,我也花了不少錢,印出所有發展後落定程式的源碼,才不怕破壞,也利於查核。中國有句俗話說:絹八百,紙一千。您就可以明白,無論電腦儲存媒體的發展有多高明,就是不如中國人講的紙一千,就是說紙張的資料能長保一千年的意思,千年之後再印成紙,又能有再一千年的保存能力。錄音帶、軟碟片、硬碟機、光碟片、USB 的 flush memory ,雲端庫存,全都沒有這種能力,戰亂一起,還都可能一切歸零。印成紙張,永恆的存活率最高。
這個程式在我們外出與好友談 Forth 技術時,如果實體表不在身邊,網上雜物一大堆,找到適當的好表也很浪費時間時,不妨就用這個程式直接印出對照表來使用。
我曾將幾百張 ANSI 標準指令表轉交給協會,只要願意踏入 Forth 行列者,都應該可以索討此表使用,永久留參。我搞出過這麼多東西後,知道自己根本不可能永遠記得住所有創作出來的使用規矩,所以,我自己也經常要參考自己出版的『使用手冊』,一輩子都要用。這個範例程式的意義也是這樣。
:
\ Example 15. ASCII Character Table
: Printable ( n -- n , convert non-printable characters to spaces )
DUP 14 < ( 7-13 are special formatting )
IF DUP 6 > ( characters not displayable )
IF DROP 32 THEN ( substitute them by space )
THEN
;
: HorizontalASCIItable ( -- )
CR CR CR
5 SPACES
16 0 DO I 4 .R LOOP ( show column header )
CR
16 0 DO ( do 16 rows )
CR I 16 * 5 .R ( print row header )
16 0 DO ( print 16 characters in a row )
3 SPACES
J 16 * I + ( current character value )
Printable
EMIT ( print it )
LOOP ( loop for next character )
LOOP ( loop for next row )
CR
;
: ht horizontalasciitable ;
: VerticalASCIItable ( -- )
CR CR CR
5 SPACES
16 0 DO I 16 * 4 .R LOOP ( show column headers )
CR
16 0 DO ( do 16 rows )
CR I 5 .R ( print row header )
256 0 DO ( do 16 columns )
3 SPACES
J I + ( current character )
Printable EMIT
16 +LOOP ( skip 15 characters between columns)
LOOP
CR
;
: vt verticalasciitable ;
\s
Usage:
Type
HorizontalASCIItable
VerticalASCIItable
or
ht
vt
第(16)個程式是根據輸入之年月日算出該日為星期幾的程式。
從現在開始,一百個範例,將進入一個新的階段,除非特別需要,後續範例程式,只要涉及數學計算,都將盡量以 ABC FORTH 的格式設計出程式。
這個程式本身的可讀性就已經相當高了,實在不需要特別解釋,但其中卻蘊含了高度的設計哲理。如果我不指點出來說明,您並不容易看得出來,因此,我只挑這方面的特點來強調。
首先,這個程式是所有利用到儒略日技術的總結,只有這種程式的寫法,能夠直接告訴大家儒略日的換算公式為何?程式就是答案,不必贅述。
其次,在正式走入 ABC FORTH 環境前,我最好把整件事情的重點,先行提出來列示於此,以利大家能夠快速進入狀況。
貼示在我的個人網頁 20180616 的網文:井田制度(# field farming system)中,曾經告訴全世界,我的系統可以精簡到只用這 11 個指令,便能寫出所有的數學計算程式。
:
1. LET 計算
2. FOR 就從
3. TO 做到
4. STEP 間隔
5. NEXT 配合
6. GOTO 跳到
7. IF 假如
8. THEN 前往
9. RUN 執行
10. PRINT 印出
11. END 結束
進一步講,只有 10 個,因為,最後的那一個 END 是強迫結束 BASIC 式環境回到 Forth 式環境所需要使用的指令。後續可以空白,也可以再添寫 FORTH 程式,如此而已。
只有 10 個要學、要記,教小學生都很容易了,所以有其特殊價值。
右邊對應列出來的中文名稱,只有 LET 被改稱為『計算』,與最原始的使用手冊內容不同,在此特別聲明。
我在長期研發系統時,考慮永久性的問題,也把將來會發展出來的語音輸入能力列入考慮,我拿 Google 的環境測試語音輸入,結果,就是這個 LET 指令,原用『令』為中文名稱時,成為無法解決的問題。中文的單個字,會有許多其他的單個字與其同音,但少有兩個字以上形成的詞會同音。所以,我就開始把 LET 改譯成『計算』,取其意義與這個指令執行的目的相關來定其名。第(16)個程式中同時列示了可執行的對應中文程式,可以看出,把『令』改成『計算』,不會影響整體精神,反而更符合程式的實際語意。試想,這個指令,若用在武器系統,讓操作者直接下口語命令要電腦執行,這時,就別再在那裡『令』、『另』難分,搞不出結果了,用『計算』就不會有此困擾。
:
\ 全英文版程式
5 INTEGERS julianDate WhatDay Year Month Day
: PrintWhatDay ( -- )
BASIC
10 IF WhatDay = 0 THEN 90
20 IF WhatDay = 1 THEN 110
30 IF WhatDay = 2 THEN 130
40 IF WhatDay = 3 THEN 150
50 IF WhatDay = 4 THEN 170
60 IF WhatDay = 5 THEN 190
70 IF WhatDay = 6 THEN 210
80 GOTO 1000
90 PRINT " This date is Sunday."
100 GOTO 1000
110 PRINT " This date is Monday."
120 GOTO 1000
130 PRINT " This date is Tuesday."
140 GOTO 1000
150 PRINT " This date is Wednesday."
160 GOTO 1000
170 PRINT " This date is Thursday."
180 GOTO 1000
190 PRINT " This date is Friday."
200 GOTO 1000
210 PRINT " This date is Saturday."
1000 END 1000
;
: MAIN ( -- )
BASIC
10 PRINT " Enter the date as Year Month Day: " CR
20 INPUTI Year , Month , Day
30 LET julianDate
= ( 367 * Year )
+ ( ( Month * 275 ) / 9 ) + Day + 1721029
- ( ( ( ( Month + 9 ) / 12 ) + Year ) * 7 / 4 )
- ( ( ( ( Year - ( ( Month + 9 ) / 12 ) ) / 100 ) + 1 ) * 3 / 4 )
40 LET WhatDay = ( JulianDate + 1 ) MOD 7
50 RUN PrintWhatDay
60 END
;
cr cr
.( Usage: main ) cr
\S
\ 全中文版程式
5 個整數變數 星期餘數 儒略日 年 月 日
: 印出星期幾 ( -- )
中文程式
10 假如 星期餘數 = 0 前往 90
20 假如 星期餘數 = 1 前往 110
30 假如 星期餘數 = 2 前往 130
40 假如 星期餘數 = 3 前往 150
50 假如 星期餘數 = 4 前往 170
60 假如 星期餘數 = 5 前往 190
70 假如 星期餘數 = 6 前往 210
80 跳到 1000
90 印出 " 這一天是星期天。"
100 跳到 1000
110 印出 " 這一天是星期一。"
120 跳到 1000
130 印出 " 這一天是星期二。"
140 跳到 1000
150 印出 " 這一天是星期三。"
160 跳到 1000
170 印出 " 這一天是星期四。"
180 跳到 1000
190 印出 " 這一天是星期五。"
200 跳到 1000
210 印出 " 這一天是星期六。"
1000 結束
;
: MAIN ( -- )
中文程式
10 印出 " 請以 年 月 日 秩序輸入日期,數字之間必須空格 : " CR
20 輸入整數 年 , 月 , 日
30 計算 儒略日
= ( 367 * 年 )
+ ( ( 月 * 275 ) / 9 ) + 日 + 1721029
- ( ( ( ( 月 + 9 ) / 12 ) + 年 ) * 7 / 4 )
- ( ( ( ( 年 - ( ( 月 + 9 ) / 12 ) ) / 100 ) + 1 ) * 3 / 4 )
40 計算 星期餘數 = ( 儒略日 + 1 ) MOD 7
50 執行 印出星期幾
60 結束
;
第(17)個程式是計算出任意階行列式值的程式。
數學問題一旦進展到矩陣的格式,就可算是將問題發展到了極致。矩陣常由聯立方程式形成,將問題聯立方程式化,等同於將問題進行系統性的綜合分析,所以說他算是極致。
處理矩陣數學的問題中,最基本的計算問題,就是算出矩陣的行列式值(determinant),此值有很多地方要用到它。手算三階以下的行列式值,還算容易辦到,四階以上,就很難使用手算獲得,這個範例因此而安排一個很規則、但很簡單的四階行列式當輸入,來測示程式。但是,我測過,要算 100x100 的行列式也不會有問題,就算要應用到現在最熱門、最流行的雲端大數據處理問題,這個程式也能使用。
測試的方法有點講究,搞矩陣數學者都知道,相依聯立方程式無解,這種矩陣的行列式值等於 0。因此,必須找出線性獨立的聯立方程式來測試程式才有意義。我長期研究這方面的問題,拜網路之賜,我很快就發現了一種所謂的魔術方陣(magic matrix)之產生方法,確定這種矩陣絕對不會線性相依,於是先自行設計出一套它的產生程式,再將輸出結果交給這個範例程式測試,任何高階的魔術方陣都被證明確實是屢試不爽。可產生魔術方陣的程式,已經公佈在我的個人網頁中,請自行查找。所以,大家可以永久放心的使用這個範例。
有許多人喜歡拿程式語言有無自我叫用的(recursive)性能來強調語言的好壞,我則不以為然,能不用此性能時,絕不使用。 Forth 可以執行 recursive 式的程式,但參數的傳遞相當困擾,就算能夠執行,也會把使用者搞得昏頭轉向、不清不楚。另外,我深入探索過 Forth ,深知凡是執行 recursive 程式時,系統都必須將前一層次的相關參數,推放到一個堆疊上去,每解完一層後,才再取回上一層的參數,進行相同的遞迴式處理。堆疊容量都是有限的,耗用太多堆疊的程式,都不是正規的、也不是很好的使用方法,危險又混亂,所以我能不用 recursive 時就盡量不用。
不幸,後來發展出來的許多程式語言,都常只用 recursive 法求解行列式值,網上很多,我不想用,只好回頭找舊書,只找到了這篇可貴的資源:
P.N.Corkett and J.D.Tinsley, Practical Programming, Cambridge University Press, 1968, p.182
是 ALGOL 語言寫成的程式,有流程圖,很容易了解解法,但不容易寫成書中漂亮的程序,如果我不直接抄改原 ALGOL 程式,我承認,自己很難創作出這麼漂亮的程式。抄改過程中,我也才發現,我自己創作的 ABC FORTH 程式語法,特別適用於這種抄改古老程式的工作,凡是古聖先賢寫成的 Algol, PL1, FORTRAN-77, BASIC, PARSCAL 程式,幾乎都能直接抄改。
簡述計算出行列式值的算法為:
利用矩陣運算理論中的線性定理,將某一列之值同乘上一個常數再加到另一列去,矩陣本身的性質不變,這樣的轉換運算也適用於兩列之間的關係,這樣的特性就被稱為線性轉換關係。亦即與『所有的解乘上任意常數後相加,所得結果亦為其解』的意義相同(這也是現行最熱門流行之量子儲存擁有線性疊加性能理論的依據)。根據這個定理,我們就可以想辦法把任何一個矩陣,轉換成對應的上右三角、左下三角、右下三角、左上三角之等效矩陣,本質不變。結果,最後就能單只把對角線上的元素之值連乘起來,這就是行列式值。這個範例中轉換成的,是右上三角矩陣。
計算很冗長,別忘了它是在處裡矩陣,古聖先賢很精明,知道算法會弱化精確度,所以算前先找出最大元素,改放到對角線的最左上方,一切以他為準,來算出能消去半個三角區域內之元素所需乘上的理想常數,才進行計算,就會比較不失精確。此外,原作者也熟知電腦表現數字時有微量的不準度,該為零時可能會不為零,必定影響後續的計算處理,所以,當計算結果小於最低精確度時,必須強迫該元素為零。這些程序,這個程式中都做了,我已全用英文加好了註解,留供永久參考。
這麼漂亮、這麼複雜的程序,憑良心講,我自己是寫不出來的,所以程式很可貴。類似這種快要失傳的程式寫法,數量非常多也非常龐大,而且都很有用,不抄改寫進入我的 ABC FORTH 系統來庫存就覺得實在可惜。我常看舊書、看舊程式,發現有很多這種東西,目前還能獲得,日後就很難講。
我已不指望能有人共襄盛舉、一起做抄改寫的工作,因為要讀懂數值分析的書本,要自行認識數學問題,再找出可貴的古老創作,才能下手工作。這樣要求別人太難了,只能自己有空、有興趣時、自己做。
我放著許多這方面的資料無法看完,只能看一點做一點,選定書本後,持之以恆的有系統進行,產品已有一大堆,只是不宜放進百例,但這種工作,恆為有意義的工作,樂趣無窮。
\ 計算任意階行列式值程式2009-08-23
INTEGER N
4 4 MATRIX AA
: INPUT-DATA
[[ N = 4 ]]
{{ AA ( 1 1 ) = 1 }} {{ AA ( 1 2 ) = 2 }} {{ AA ( 1 3 ) = 3 }}
{{ AA ( 1 4 ) = 4 }}
{{ AA ( 2 1 ) = 2 }} {{ AA ( 2 2 ) = 3 }} {{ AA ( 2 3 ) = 4 }}
{{ AA ( 2 4 ) = 1 }}
{{ AA ( 3 1 ) = 3 }} {{ AA ( 3 2 ) = 4 }} {{ AA ( 3 3 ) = 1 }}
{{ AA ( 3 4 ) = 2 }}
{{ AA ( 4 1 ) = 4 }} {{ AA ( 4 2 ) = 1 }} {{ AA ( 4 3 ) = 2 }}
{{ AA ( 4 4 ) = 3 }} ;
INTEGER I INTEGER J INTEGER K
INTEGER P INTEGER Q
REAL A REAL D
: SHOW-AA BASIC
10 FOR I = 1 TO 4
20 FOR J = 1 TO 4
30 PRINT { AA ( I J ) }
40 NEXT J
50 NEXT I
60 END ;
: (DET) BASIC
\ A program to evaluate a determinant by the leading diagonal method,
\ using largest pivots.
\ 10 RUN INPUT-DATA
20 LET { D = 1 }
30 FOR K = 1 TO ( N - 1 )
40 LET P = K
50 LET Q = K
60 LET { A = ABS ( AA ( K K ) ) }
70 FOR I = K TO N
80 FOR J = K TO N
90 IF { ABS ( AA ( I J ) ) > A } THEN 110
100 GOTO 140
110 LET P = I
120 LET Q = J
130 LET { A = ABS ( AA ( I J ) ) }
140 NEXT J
150 NEXT I
\ End of search for largest element.
160 IF P <> K THEN 180
170 GOTO 230
180 FOR J = K TO N
190 LET { A = AA ( P J ) }
200 LET { AA ( P J ) = AA ( K J ) }
210 LET { AA ( K J ) = NEGATE ( A ) }
220 NEXT J
\ End of interchange of rows P and K.
230 IF Q <> K THEN 300
240 GOTO 270
250 FOR I = K TO N
260 LET { A = AA ( I Q ) }
270 LET { AA ( I Q ) = AA ( I K ) }
280 LET { AA ( I K ) = NEGATE ( A ) }
290 NEXT I
\ End of interchange of columns Q and K.
\ Largest element is now the pivot.
300 LET { D = D * AA ( K K ) }
310 IF { ABS ( D ) < 1.0e-10 } THEN 430
320 FOR I = K + 1 TO N
330 FOR J = K + 1 TO N
340 LET { A = AA ( K J ) * AA ( I K ) / AA ( K K ) }
350 LET { AA ( I J ) = AA ( I J ) - A }
360 IF { ABS ( AA ( I J ) ) < ABS ( A ) * 1.0e-10 } THEN 380
370 GOTO 390
380 LET { AA ( I J ) = 0 }
390 NEXT J
400 NEXT I
\ End of reduction to upper triangular form.
\ 405 PAUSE
410 NEXT K
420 LET { D = D * AA ( N N ) }
430 IF { ABS ( D ) > 1.0e-10 } THEN 450
440 LET { D = 0 }
450 LET { D = D }
\ Determinant is got in D.
\ 460 PRINT { D }
470 END ;
: DET BASIC
10 RUN INPUT-DATA
20 IF N = 1 THEN 40
30 GOTO 60
40 LET { D = AA ( 1 1 ) }
50 GOTO 110
60 IF N = 2 THEN 80
70 GOTO 100
80 LET { D = AA ( 1 1 ) * AA ( 2 2 ) - AA ( 1 2 ) * AA ( 2 1 ) }
90 GOTO 110
100 RUN (DET)
110 PRINT { D }
120 END ;
cr cr
.( Usage : ) cr
.( det ) cr
第(18)個程式是利用 DO ----- LOOP 指令印出以指定字元顯示的一個方塊。
這個程式沒有數學計算,所以不必強用 ABC Forth 格式寫程式。 使用 ABC Forth 時的基本精神是:使用時,並不應該因為有了 ABC Forth ,就得什麼程式都要用 BASIC 的格式寫程式。
我們在閱讀『FORTH入門』(Startiing FORTH)時,最先學會的程式寫法,就是使用 42 emit 在螢幕上輸出一個星號。那樣教 Forth ,也是在告訴大家,FORTH 的輸出能力非凡,比當時的任何程式語言都好,不只是控制螢幕輸出,連自動控制的輸出也都是這樣。因為,電腦的自動控制,只是在將一種位元花樣,放到指定記憶體的位址而已,其意義,與控制螢幕輸出是同一個道理。
我在收集範例程式時,考慮到螢幕輸出的範例,必定陸陸續續的會出現新發展與新構想,所以,只要與最原始的螢幕輸出相關的範例,我就收集,取其具有漸進式的教育效果,我才將其列為範例。後面還有更高級的螢幕輸出範例,可以輸出很複雜的字元圖,例如:第(85)個範例,可以以字元繪製出很複雜的 Mandelbrot 圖型。
有些範例,雖在前面已經用過,但發展到後來,我發現像寫文章一樣,把程式寫得更有規格,會更易於永久有用。於是,就再把前面已經用過的範例,重寫後再度編入百例。後面,您可以看到前面介紹的求行列式值程式的另種編排格式,又出現在第(92)個範例中,請不要覺得奇怪,新格式的範例程式,有其用途。
這個範例程式並不難看懂,不必深入解釋。但請實際執行一下,看看結果。您便可以發現螢幕輸出時,執行速度並不快,這一要點,倒是值得強調。
這個程式中的字元輸出,是逐個送出去顯示的,所以速度很慢。我在設計必須印出很多位數的數字時,如果採用每算出一個數字就執行一次印出,那麼,速度就會變慢。但是,如果一次就將所有位數的計算算完,待印之字元都暫時放在一個指定的緩衝區內暫存,印出時則一次印出,速度就會很快。
這是說明這個範例時值得特別指出性能差異的教材。
:
\ (28)ASCIIBox.f
\ 以指定之字元,印出指定寬與高之長方形,
: star 42 emit ;
: top ( width -- )
0 do star loop cr ;
: bottom ( widty -- )
top ;
: middle ( height -- )
star
2 - 0 do space loop
star cr ;
: box ( width height -- )
cr over
top
2 - 0 do dup middle loop
bottom ;
page cr cr
.( Usage Example: ) cr
.( 40 10 box ) cr
第(19)個程式是一個求 pi 值的程式。
發展數位計算技術,都會拿求質數與求 pi 的問體考驗自己,我也不例外,題目的種類繁多,還時有新創意被發表出來,追蹤它可以忙得您不亦樂乎看都看不完。
這一範例,只能討論重點。
從大學起,我讀的是 1969 年第三版 MIT 教授 Thomas 的微積分教科書, chapter 17.6, p.591, 談到了 Computation of pi ,書中列有計算出 pi 的詳細公式,由於是運用 Taylor 級數展開式獲得的結果,我還能了解,並用程式來實現計算出無限位數的成果。許多別種無窮級數公式的計算法,也能寫成程式,但我也沒真正地做過。
算 pi 的方法有很多種,目的則只有一個,就是想獲得『快速收斂的計算』技術,以便在求取非常多的位數時派得上用場。這個範例使用一種稱之為水龍頭滴水(spigot)法的演算法求 pi 。他的基本公式定義為下式:
:
\ pi.f
\ 改寫自kforth之程式,只能run一次。故編譯完成後直接run。
\ 最後修正日期:20140114
\ Great Computer Language Shootout
\ http://shootout.alioth.debian.org/
\ contributed by Albert van der Horst, Ian Osgood
\ read NUM from last command line argument
\ 0. argc @ 1- arg >number 2drop drop constant NUM
\ 儲存計算結果的資料結構,靠這個常數來決定,編譯後就不能改,故用常數。
1300 constant NUM \ ***
\
\ Arbitrary precision arithmetic
\ A p-number consists of a count plus count cells, 2-complement small-endian
\
\ Shorthand.
: p>size ( pn -- size ) POSTPONE @ ; IMMEDIATE
: p>last ( pn -- msb ) DUP p>size CELLS + ;
: [I] POSTPONE I POSTPONE CELLS POSTPONE + ; IMMEDIATE
\ Give sign of p
: p0< ( p -- flag ) p>last @ 0< ;
\ Copy a p-number to another buffer
: pcopy ( src dst -- ) OVER p>size 1+ CELLS MOVE ;
\ Check for overflow, extend the p-number if needed
: ?carry ( carry p -- ) 2DUP p0< <> IF 1 OVER +! p>last ! ELSE 2DROP THEN ;
\ In-place multiply by an unsigned integer
: p* { n p -- }
p p0< 0. ( sign dcarry )
p p>size 1+ 1 DO
p [I] @ ( digit )
n UM* D+ SWAP ( carry digit )
p [I] ! 0
LOOP
ROT n UM* D+ DROP p ?carry ;
\ Ensure two p-numbers are the same size before adding
0 value signn \ *** 取消二度使用局部變數
: extend { p n -- }
p p0< to signn \ { sign } \ *** sign to signn
p p>size 1+ n p +!
p p>size 1+ SWAP DO signn p [I] ! LOOP ; \ ***
: ?extend ( p1 p2 -- p1 p2 )
OVER p>size OVER p>size - ?DUP IF
DUP 0< IF >R OVER R> NEGATE
ELSE OVER SWAP
THEN extend
THEN ;
\ In-place addition of another p-number
: p+ ?extend { src p -- }
src p0< p p0< 0. ( sign sign dcarry )
p p>size 1+ 1 DO
p [I] @ 0 D+
src [I] @ 0 D+ SWAP
p [I] ! 0
LOOP
DROP + + p ?carry ; \ add signs, check for overflow
\ In-place subtraction of another p-number
: p- ?extend { src p -- }
src p0< p p0< 0. ( sign sign dcarry )
p p>size 1+ 1 DO
p [I] @ 0 D+
src [I] @ 0 D- SWAP
p [I] ! S>D
LOOP
DROP + + p ?carry ; \ add signs, check for overflow
\
\ pi-spigot specific computation
\
\ approximate upper limit on size required (1000 -> 1166)
NUM 2* CELLS constant SIZE
\ Current z transformation
CREATE aq 1 , 1 , SIZE ALLOT
CREATE ar 1 , 0 , SIZE ALLOT
\ "as" identical zero and remains so
CREATE at 1 , 1 , SIZE ALLOT
\ Generate non-zero parts of next matrix ( K 4K+2 2K+1 )
VARIABLE KK \ *** K to KK
: generate ( -- q r t ) 1 KK +! KK @ DUP 2* 1+ DUP 2* SWAP ; \ ***
\ HERE is used as a temporary p-number
\ Multiply z from the left
: compose< ( bq br bt -- )
DUP at p* ar p* aq HERE pcopy HERE p* HERE ar p+ aq p* ;
\ Multiply z from the right
: compose> ( bt br bq -- )
DUP aq p* ar p* at HERE pcopy HERE p* HERE ar p- at p* ;
\ Calculate z at point 3, leaving integer part and fractional part.
\ Division is by multiple subtraction until the fractional part is
\ negative.
: z(3) ( -- n pfract )HERE aq OVER pcopy 3 OVER p* ar OVER p+
0 BEGIN SWAP at OVER p- DUP p0< 0= WHILE SWAP 1+ REPEAT ;
\ Calculate z at point 4, based on the result for point 3
\ and decide whether the integer parts are the same.
: z(4)same? ( pfract -- flag ) aq OVER p+ p0< ;
: pidigit ( -- nextdigit)
BEGIN z(3) z(4)same? 0= WHILE DROP generate compose< REPEAT
1 OVER 10 * 10 compose> ;
: .digit ( -- ) pidigit [CHAR] 0 + EMIT ;
\ : .count ( n -- ) .\" \t:" 1 U.R CR ; \ ***
: .count ( n -- ) 3 spaces ." :" 1 U.R CR ;
\ Spigot n digits with formatting
: spigot ( digits -- )
cr 0 \ ***
BEGIN 50 + 2DUP > WHILE \ ***以下全原為10改成50
50 0 DO .digit LOOP DUP .count \ ***
REPEAT
2DUP 50 - DO .digit LOOP OVER - SPACES .count ; \ ***
((
: TypeCountingRuler ( -- ) \ *** 自建底標尺
cr ." =========1=========2=========3=========4=========5"
cr ." 12345678901234567890123456789012345678901234567890"
cr ." ========(c) 2014 Copyright, Counting Ruler========"
cr ;
))
NUM spigot TypeCountingRuler \ bye \ *** 取消bye
沒有留言:
張貼留言