一百個例題(01~10)
一百個例題(01~10)
Ching-Tang Tseng
Hamilton, New Zealand
1 August 2024
文學創作者想把著作搞到最高境界時,最後,都想寫詩。搞Forth的前輩,程式發展到後來,也會想把詩歌融入Forth。
:
第(01)個程式的內容就在做這件事情。題材的原始出處,來自Forth界最有名的期刊:FORTH空間(FORTH DIMENSIONS),程式刊載於第二卷第一期第9頁。
:
這個程式執行時打算朗誦(recite)一首詩歌,程式的結構很簡單,利用Forth程式語言中輸出文字的相關指令來印出文字。程式的特點在強調Forth在設計程式時,因為指令用字可以隨便採用任何單字或符號,中英文都沒有限制,只要沒有空格,就能當作新設計指令的名字。因此,程式中採用了兩個區塊的設計,一區塊只印出簡單的片語,另一區塊則印出較複雜的片語,最後,利用變化的組合情況,印出朗誦的詩文。
:
印出時,人機介面單依靠一個叫KEY的指令控制,但不管使用者壓什麼鍵,就繼續執行後續程式。變化的組合也只單靠
:
IF ----- ELSE ----- THEN
的結構來完成,第一次用時,等於沒用,也能成為程式內容,第二次用時,才是簡單使用的例子。這是一個從初步課程選出來的簡單程式,可以看看內容後自行體會使用Forth時的基本精神。
:
我設計過語音輸出的功能,而且截至目前為止,好像仍是獨門技術,我只在網上刊出過使用範例,沒公開過這套技術的源程式。為什麼?因為語音輸出不是Forth的本質,凡想輸出語音前,作業系統都得先行安裝語音輸出套裝軟體,才能使用Forth控制語音輸出。這個程式實際上可以修改成讓Forth系統直接輸出語音,我能辦得到,但在微軟作業系統中的語音輸出軟體很難用,種類也不夠多,而且直到2005年以後,才有人捐獻介面驅動程式給Win32Forth使用,又因程式的工作方式,必須使用物件導向程式(Object Oriented Programming)的方式寫程式,很難將成果固定下來,每次使用時,都得重新載入自己的設計,才能執行出結果。這樣用,我就只好自己使用了。我的網文刊出過廣告介紹式的文章,在Linux環境就比Windows’好太多了,能夠固定發展成果。另外,我在網文中也提過,英文語音輸出的資源非常豐富,中文卻非常缺乏,這事,需要中國人以國家的力量來發展才行。一方面,中文的語音輸出不像拼音文字那麼容易處理,另方面,中文方言太多,詞句的破音字也多,處理系統必然龐大,新時代的電腦,雖然不在乎記憶體的用量,仍得考慮設計系統時多樣性的複雜度,若非以國力推行出標準用法,實在難有理想結果。
\ Example 1. The Theory That Jack Built
((
'The Theory that Jack built' was published by W. F. Ragsdale in the Forth Dimensions, Volume II, No. 1, p. 9. It was taken from 'The Space Child's Mother Goose' by Frederick Winsor. It defines phrases which are used to build sentences and stanza. This is a good example to show how to build a sizable program from small modules in Forth.
First we define some simple phrases:
))
: the ." the " ;
: that cr ." That " ;
: this cr ." This is " the ;
: jack ." Jack Builds" ;
: summary ." Summary" ;
: flaw ." Flaw" ;
: mummery ." Mummery" ;
: k ." Constant K" ;
: haze ." Krudite Verbal Haze" ;
: phrase ." Turn of a Plausible Phrase" ;
: bluff ." Chaotic Confusion and Bluff" ;
: stuff ." Cybernatics and Stuff" ;
: theory ." Theory " jack ;
: button ." Button to Start the Machine" ;
: child ." Space Child with Brow Serene" ;
: cybernatics ." Cybernatics and Stuff" ;
\ Now we will build more complicated compound phrases.
: hiding cr ." Hiding " the flaw ;
: lay that ." Lay in " the theory ;
: based cr ." Based on " the mummery ;
: saved that ." Saved " the summary ;
: cloak cr ." Cloaking " k ;
: thick if that else cr ." And " then
." Thickened " the haze ;
: hung that ." Hung on " the phrase ;
: cover if that ." Covered "
else cr ." To Cover "
then bluff ;
: make cr ." To Make with " the cybernatics ;
: pushed cr ." Who Pushed " button ;
: without cr ." Without Confusion, Exposing the Bluff" ;
: rest ( pause for user interaction )
." . " ( print a period )
10 SPACES ( followed by 10 spaces )
KEY ( wait the user to press a key )
DROP CR CR CR ;
: recite ( recite the poem )
cr this theory rest
this flaw lay rest
this mummery hiding lay rest
this summary based hiding lay rest
this k saved based hiding lay rest
this haze cloak saved based hiding lay rest
this bluff hung 1 thick cloak
saved based hiding lay rest
this stuff 1 cover hung 0 thick cloak
saved based hiding lay rest
this button make 0 cover hung 0 thick cloak
saved based hiding lay rest
this child pushed
cr ." That Made with " cybernatics without hung
cr ." And, Shredding " the haze cloak
cr ." Wrecked " the summary based hiding
cr ." And Demolished " theory rest
;
((
Forth words used in this example are collected in the following list so you can see how they are used.
CR ( --) Start a new line on the screen.
." xxxx" ( -- ) Print the string xxxx till the second quote.
KEY ( -- char ) Wait for a keystroke, and return the
ASCII code of the key pressed.
DROP ( n -- ) Discard the number.
SPACE ( -- ) Display a blank.
SPACES ( n -- ) Display n blanks.
IF ( f -- ) If the flag is 0, skip the following
instructions up to ELSE or THEN. If
flag is not 0, execute the following
instructions up to ELSE and skip to
THEN.
ELSE ( -- ) Skip the following instructions
up to THEN.
THEN ( -- ) Terminate an IF-ELSE-THEN structure
or an IF-THEN structure.
The instructions KEY DROP will be explained fully in a later lesson. For this moment, it is sufficient to say that they cause the computer to pause to give you time to read the poem. When you press any key on the keyboard, the computer will continue executing the next instructions in the definition. 'rest' will be executed at the end of every stanza.
RECITE
prints out the whole thing in a sequence of stanzas.
))
cr cr .( Usage : RECITE ) CR CR
第(02)個程式,是以印出文字的方式來介紹兩項 Forth 特色。
:
其一,是程式中使用了一個人機操作介面常用的 KEY 指令,作為對答程式的設計範例。
:
系統執行到這個 KEY 指令時,會耗住不動,等待使用者從鍵盤輸入一個按鍵,包括需要同時壓下兩個以上的複合鍵,然後在堆疊上留下對應於此鍵的美國資訊交換標準碼(ASCII code),這個指令執行前後的堆疊表示式為:
:
KEY ( — n )
:
程式 KEY 32 OR 121 = 的意義,是使用者在壓下大寫的 Y 或小寫的 y 時,堆疊上會留下一個代表為『真』的邏輯值,現行國際標準值是『 -1 』。
:
其二,是介紹 IF ----- ELSE ----- THEN 邏輯分支結構指令的用法。
:
由於程式流程使用文字表示,您直接操作,執行出結果,就能了解流程的走向。
:
注意到我在輸入指令、操作系統、得到結果的展示,都只用小寫的英文字母了嗎?
:
Forth 系統通常都是一種專業術語上叫做大小寫不區分(case insensitive)的 Forth 系統,大寫字母的英文是 upper case letters,小寫字母的英文是 lower case letters,故有此術語。
:
但請注意,系統只在查找系統中一般的 Forth 指令時,可以大小寫不區分。若要叫用作業系統的 API,使用到其變數或常數的名稱時,大小寫還是必須遵照規矩輸入才行。
:
你可以自己執行 ascii 或 ASCII 或 AscII 試試看,體會一下 case insensitive 的意義。以後在國際論壇中見到別人提及 case insensitive 的術語時,您就可以明白它的意思。
:
\ Example 2. Advisor
\ This is a conversational game in which the computer will dispense advises on various problems
\ such as sex, health, money and job.
\ It is adapted from a program in 'Basic Computer Games' by David Ahl, p. 82.
: Advisor
CR ." Hello! My name is Creating Computer."
CR ." Hi there!"
CR ." Are you enjoying yourself here?"
KEY 32 OR 121 =
IF CR ." I am glad to hear that."
ELSE CR ." I am sorry about that."
CR ." maybe we can brighten your visit a bit."
THEN
CR ." Say!"
CR ." I can solved all kinds of problems except those dealing"
CR ." with Greece. What kind of problems do you have"
CR ." ( sex, health, money or job )?"
CR
;
: question
CR ." Any more problems you want to solve?"
CR ." What kind ( sex, job, money, health ) ?"
CR
;
: sex CR ." Is your problem TOO MUCH or TOO LITTLE?"
CR
;
: too ; ( noop for syntax smoothness )
: much CR ." You call that a problem?!! I SHOULD have that problem."
CR ." If it really bothers you, take a cold shower."
question
;
: little
CR ." Why are you here!"
CR ." You should be in Tokyo or New York of Amsterdam or"
CR ." some place with some action."
question
;
: health
CR ." My advise to you is:"
CR ." 1. Take two tablets of aspirin."
CR ." 2. Drink plenty of fluids."
CR ." 3. Go to bed (along) ."
question
;
: job CR ." I can sympathize with you."
CR ." I have to work very long every day with no pay."
CR ." My advise to you, is to open a rental computer store."
question
;
: money
CR ." Sorry! I am broke too."
CR ." Why don't you sell encyclopedias of marry"
CR ." someone rich or stop eating, so you won't "
CR ." need so much money?"
question
;
cr cr
.( You can start the conversation by typing: ADVISOR )
\S
The conversation takes advantage of the interactive nature of Forth,
as the responses from the user are defined as Forth instructions.
The user seems to be answering questions from the computer,
while he is actually typing instructions to the computer.
These instructions print appropriate advises to the user.
第(03)個程式叫作匯率換算(money exchange)程式,這個程式展示 Forth 中特有的 */ 計算指令。
:
所有的 Forth 系統,在建立初期,都只能處理整數的運算,所以系統才能比別種程式語言容易建立。
:
程式語言的發明人 Charles H. Moore 堅持終生不用浮點計算。為了提高運算後的精確度,他創作了 */ 這個先乘上去再除下來的特殊指令,指令的堆疊規格是:
:
*/ ( n1 n2 n3 — n4 ) 其中,n4 = ( n1 * n2 ) / n3 。
:
國際鈔票的兌換,免不了一定要用到小數點來運算,否則誤差太大了,光取整數乘除後的算法,必定沒有人肯接受這樣的換算結果。但是,另一方面,如果硬要採用浮點運算來精算,那麼,元、角、分算到角就夠了,也就是只取兩位有效數字。分以下就別用了,算出來後,若還硬要使用,該付錢的這一方,也給不出這種錢來。
:
*/ 指令能讓計算結果變得更準,是因為這個指令在執行上列計算的過渡階段中,採用了雙整數暫存相乘的數字,然後才再除回單整數,這樣,就能提高想要獲得的較高精確度,當然,這等同於將運算數字在無形中放大位數了,才能有此結果。
:
從設定的數字可以看出,在設計這個程式時,台幣對美金的匯率大約是 25 比 1,所以有乘上 10 再除以 245 的設計。今天 20240805 的美金匯率是 32.315 至 32.985,大約是 32.5,設定的數字 245 就得重新修正成 325。您可以跑一跑這個程式,體會一下 */ 指令的效果,想真用,就請自行修改設計內容。
:
\ Example 3. Money Exchange
: NT ( nNT -- $ ) 10 245 */ ;
: $NT ( $ -- nNT ) 245 10 */ ;
: RMB ( nRMB -- $ ) 100 547 */ ;
: $RMB ( $ -- nJmp ) 547 100 */ ;
: HK ( nHK -- $ ) 100 773 */ ;
: $HK ( $ -- $ ) 773 100 */ ;
: gold ( nOunce -- $ ) 356 * ;
: $gold ( $ -- nOunce ) 356 / ;
: silver ( nOunce -- $ ) 401 100 */ ;
: $silver ( $ -- nOunce ) 100 401 */ ;
: ounce ( n -- n, a word to improve syntax ) ;
: dollars ( n -- ) . ;
\s
Usage:
5 ounce gold .
10 ounce silver .
100 $NT .
20 $RMB .
1000 NT 500 HK + .s
320 RMB + .s
dollars ( print out total worth in dollars )
1000 NT 500 HK + 320 RMB + .s
$HK dollars ( convert total to Hong Kong dollar and print it)
第(04)個程式是溫度轉換程式,這個主題,常被用來當作各種程式語言的精簡範例。
:
我特別找出 1990 年元月一日,曾在 F-PC Forth 上發展過的 ARITH.SEQ 舊程式,檔案中就有這麼一個小範例:
:
INTEGER C
INTEGER F
: CtoF ( n1 — n2 )
ADDRESS-OF C !
[[ F = 32 + ( C * 9 / 5 ) ]]
F ;
這個程式,經過 34 年後,照樣能在我提供給各位的 ABC Forth 660 系統中執行。
:
換句話說,34 年前,我就醉心於發展讓 Forth 系統擁有中算符處理能力的發展工作了。只是當時還不太具有必須運用數學體系概念設計系統的觀念,當時只做到能達到中算符功能的目的就算了。
:
\ Example 4. Temperature Conversion
: F>C ( nFahrenheit -- Celsius )
32 -
10 18 */
;
: C>F ( nCelsius -- nFahrenheit )
18 10 */
32 +
;
\s
Usage:
90 F>C . ( shows the temperature in a hot summer day and)
0 C>F . ( shows the temperature in a cold winter night.)
第(05)個程式展示的主要宗旨有兩個:一是介紹為什麼要活用堆疊操作指令? 另一個是教您如何在單列程式後面添加註解。
:
這個程式的題目是:如何根據兩個頂點的座標來設計出獲得長方形面積、中央座標、邊長的程式?
:
問題是大眾化的例子,不需要特別解釋。工程上,卻有無限多的問題屬於這種類型。程式設計的方法,要先搞清楚問題,取得公式後,再按照公式設計程式,才能確保不出差錯與簡單。
:
分析問題後,可以發現,任何長方形,若以座標點來表示,只需要給兩個對角點的座標,就可以完全的決定長方形,問題也才能具有其唯一性。因此,這個程式,就限定輸入的數據,只能是任意兩個點的座標值。
:
兩個頂點的座標值,共擁有四個參數: x1, y1, x2, y2 。算面積,算邊長,求中央點的座標,都要用這四個參數來計算,使用參數的秩序可以不是固定的,於是,參數擺在堆疊上的秩序,被用來計算時,就有變化上的要求了,如此一來,堆疊操作指令就得發揮作用。
:
根據我長期使用 Forth 的經驗,很少有問題需要使用四個以上的參數。若有,則最好改搞陣列來取放數據,別再單靠堆疊。大部分的數學元素,最多也只用兩個單元,如雙整數、浮點數、單個複數...等等都是,只有我在設計分數系統時,才碰到需要單個元素就得耗用 3 個單元來存放數字的問題。
:
最多使用四個參數的普遍情況,使得堆疊操作指令,只需提供 DUP, DROP, SWAP, OVER, ROT 這幾個,大約就夠用了。新時代的程式設計,也很少再用到 PICK 與 ROLL,尤其是 ROLL,早已久不見此指令存在於程式中了。這有兩個原因,一是確實很少用到。另外就是以前的標準與後來的標準差了一個計量值,以前使用 3 PICK, 4 ROLL時,現在都得改用 2 PICK, 3 ROLL了。這樣的混淆,讓許多古聖先賢公告過的公益程式,在再度被使用到時,產生了極大的困擾。不是老手,感覺不出這種無聊修改所造成的痛苦。我很痛恨後來亂訂標準的無聊敗類,尤其是這種計量只差 1 的問題,毫無必要。他們想斷別人傳承的根,卻在世上留下了萬世的臭名。
:
主要被處理的元素,最多常只使用兩個單元的情況,也使得後來的堆疊操作指令,出現了 2DUP, 2DROP, 2SWAP, 2OVER, 2ROT 的固定設計,甚至於在浮點系統中,還改以FDUP, FDROP ..... 等等指令來對應。這些延伸性的設計,本就不該被訂定為標準。強行被訂定為標準,是多餘的。所以,我在 2008 年貼出在個人網頁上的首篇『FORTH83標準指令』貼文,就永不撤文,擺在那裡,恆有用處,那才是根本精神。
:
這個範例程式中的另一特色,是以『 ( ---------- )』的方式將說明包裝起來,列於最後面,擺整齊。如果程式將堆疊的數字翻來覆去,搞到設計者也看不太清楚堆疊狀況時,您就可以學著這樣使用,以便標示自己的設計,利於日後回查。
:
\ Example 5. Rectangles
: area ( x1 y1 x2 y2 -- area )
ROT - ( x1 x2 y2-y1 )
SWAP ROT - ( y2-y1 x2-x1 )
* ( area )
;
: center ( x1 y1 x2 y2 -- x3 y3 , center coordinates )
ROT - 2/ ( x1 x2 y3 )
SWAP ROT - 2/ ( y3 x3 )
SWAP ( x3 y3 )
;
: sides ( x1 y1 x2 y2 -- sides )
ROT - ABS ( x1 x2 y2-y1 )
SWAP ROT - ABS ( y2-y1 x2-x1 )
+ ( sides )
;
\s
Usage:
0 0 100 200 area .
50 75 1000 1350 center . .
50 75 1000 1350 sides .
第(06)個程式的主要功能,在介紹多重條件下,如何產生邏輯比較後分支?題目的問題是:設計氣候報告,輸入溫度大於華氏85度時輸出為太熱,低於55度時輸出為太冷,居中時輸出為剛剛好。
:
\ Example 6. Weather Reporting
: weather ( nFahrenheit -- )
DUP 85 >
IF ." Too hot!" DROP
ELSE 55 <
IF ." Too cold."
ELSE ." About right."
THEN
THEN
;
\s
You can type the following instructions and get some responses from the computer:
90 weather Too hot!
70 weather About right.
32 weather Too cold.
能執行邏輯判斷後條件分支,是程式語言三大支柱之一,另兩個,則是能逐步執行,與能循環執行。我設計 ABC Forth 時也得驗證出具有這三項功能,才能用來解決所有的數學計算問題。今天,除了學學這個 Forth 程式外,我改用 ABC Forth 寫出這個程式,實現同樣的基本要求,然後解說 ABC Forth 與傳統 Forth 程式語法間的不同。
:
Forth 已發展成語言為結構化的規格,早期的 Forth 使用者,還常強調不改系統照樣能突破結構化的技術,後來就不強調了。現代的 Forth 還能辦得到,但也很少人談論與應用這種性能。我的 ABC FORTH 執行起來能亂跳,就是利用 Forth 的這種天性使然。請不要以為不結構化不好,它有一定要存在的絕對必要性,最基本的組合語言是一切程式語言的根本,它,就得能夠亂跳。我的 ABC Forth 中,能亂跳的指令是 GOTO,但實際上 IF ----- THEN 中的 THEN 也能亂跳。
:
我先把程式列在這裡再解釋:
:
integer t
: T? ( n -- )
[[ t ]] !
BASIC
10 if t > 85 then 50
20 if t < 55 then 70
30 print " About right. "
40 goto 100
50 print " Too hot! "
60 goto 100
70 print " Too cold. "
100 end ;
比較兩套程式,功能完全沒有問題,請自行copy這一段程式,附貼到第(06)個程式的後面,載入程式測試後便知。
:
上列程式中,[[ t ]]能夠取得整數變數 t 的位址。ABC Forth 程式在尚未執行到 BASIC 這個指令前,可以完全只使用傳統 Forth 的語法寫程式,但在 basic 至 end 間,就得使用 BASIC 程式語言的語法寫程式。若在其中仍想要用 Forth 語法寫程式,我自創了一個叫作 RUN 的 BASIC 語法專用指令,可以辦到這項要求,它的設計內容,就是叫用 INTERPRET 來完成,這裡還不急著介紹其用法,以後再說。
:
Forth 邏輯判斷後分支的 IF ----- ELSE ----- THEN 整組指令結構,是在 IF 的前面進行邏輯運算,留下結果供 IF 使用而分支。 BASIC 邏輯判斷後分支的設計,在我的 ABC Forth 中,只有一種用法,就是只能在 then 的前面進行邏輯運算,算完,供 then 使用而分支。而且,分支的要件是只能分支到後續指定的標號列去,沒有其他任何指令,所以,我前述說到的 then 就能亂跳。但,回跳,就得用標號的負數,這樣使用的例子,以後提到時再說。
:
這只是一個小範例,卻展示了使用 BASIC 語法設計的程式,能夠完全被融入 Forth 系統的特異功能。上列設計好的 T? 指令,完全能像任何其他純用傳統 Forth 語法設計出來的程式那樣,被後來的程式直接叫用。所以,我把堆疊規格也標示在 T? 後面:( n — ),跟原設計出來的 weather 指令規格完全一樣。
:
由於 ABC FORTH 是編譯(compile)式而非執譯(interpret)式的語言,所以執行速度不會變慢。而且,我的設計內容不採用傳統 BASIC 系統的設計方式,程式被執行時,系統裡面沒有將程式一列一列拿來分析的剖析器(parser),而是根據 Charles H. Moore 設計的編譯器(compiler)直接編譯出來的,所以執行速度跟 Forth 一樣快。您若有興趣,可以使用 Win32Forth 中的 see 指令直接看看 T? 被編譯後的內容,執行 see T?,就會有結果,但很難看懂,編譯完畢後,就沒有標號了。所以,程式也不會耗費太多的記憶體,其本質根本就還是個 Forth。
:
多重邏輯判斷後分支的問題,在任何程式語言中,都是個麻煩問題,分支要求一多,就會把流程搞混,程式就很難看懂。 為此,FORTH 後來就發展出 CASE ----- ENDCASE 的語法,來解決這方面的問題。1994 年以後,這一組指令已被列為標準規格。雖已為標準,但尚力有不逮,碰到條件是區域而不是單純一個數值能表達的狀況時,仍得更改設計,所以也不是很好用,反而是我設計的這種分支方法,能涵蓋所有的層面。這一個簡單範例只有三個分支,還不需要使用 CASE 來處理,它只展示兩層的分支程式該怎麼寫?
:
我們討論的題目內容不太複雜時,我會趁機討論一些程式語言的哲理,題目複雜時,就少談哲理。
:
第(07)個程式介紹以程式印出九九乘法表。
:
程式語言只有三大支柱,第三個,是能夠循環執行。這個程式就是個使用循環指令 DO ----- LOOP 的例題。
:
\ Example 7. Multiplication table
: onerows ( row -- )
CR
DUP 3 .R 3 SPACES
13 1
DO I OVER *
4 .R
LOOP
DROP ;
: Table ( -- )
CR CR 6 SPACES
13 1
DO I 4 .R LOOP ( display column numbers )
13 1
DO I onerows
LOOP
;
\s
usage:
table
DO ----- LOOP 是一種定量式的循環,也就是說它的起始與結束之指標數值是固定的。例如:從 1 做到 10 這樣的環路固定量,環路指標在環路中就會從 1 自動變化到 10 為止。相關於這種環路的指令還有好幾個,這個例題內沒有用到,我就不提。實際上,我不太喜歡使用這種用法設計程式,因為它存在著不少缺陷。例如: Forth 把指標處理的不太合乎人們的直覺,當您想印出 1 到 10 時,該給的上下限是 11 與 1,要倒過來給,上限還得多加了 1。又如:我在設計許多應用程式時,很不習慣於上下限指標相同時環路還會至少仍做一次。如此一來,我在設計函數時,常碰到有上下限一樣程式便不得執行之要求也得被包括的情形,結果就出差錯了。必須改用非標準的 ?DO 指令來取代 DO ,才能解決問題。反對將 ?DO 列為標準的人,不懂得這些問題。還有其他問題,以後遇到時再談。這些不好用的問題,我若改成以 BASIC 格式設計程式,就能解決問題。
:
這個程式,可以改寫成以 ABC FORTH 的格式重新設計,我曾將它寫進貼文,貼出於我的個人網頁,就在2018年2月15日 星期四刊出的『 Calculation 』一文中,請參考:
:
https://forthfortnight.blogspot.com/2018/02/
Calculation
Ching-Tang Tseng
Hamilton, New Zealand
16 February 2018
ilikeforth@gmail.com
http://forthfortnight.blogspot.com
文中程式如下:
3 integers I J K
: table3 ( -- )
BASIC
10 RUN CR 2 SPACES
20 FOR I = 1 TO 12
30 RUN I 4 U.R
40 NEXT I
50 RUN CR
60 FOR J = 1 TO 12
70 RUN CR 2 SPACES
80 FOR I = 1 TO 12
90 LET K = I * J
100 RUN K 4 U.R
110 NEXT I
120 NEXT J
130 END
;
在 ABC Forth 中,我設計的環路格式,用法如下:
FOR I = 起始值 TO 終止值 STEP 間隔值
——————
( 在此區域內使用指標 I )
——————
NEXT I
另外,設計此系統時,我把正在使用中的兩個上下限指標值,設計成暫時存放在普通的數據堆疊上,不像傳統的 Forth 系統是放在回返堆疊上。因此,使用者就很容易自行破壞環路,可以自由地跳出環路去到想要跳去的地方。跳出去時,執行一次 2DROP 就可以亂 GOTO 了。這個功能很有用處,在大量查找質數時,為讓程式能節省執行次數亦即節省時間,就能運用這種程式設計的技巧。它,真的是很有用處。
:
從上列改寫出來的程式中,就能注意到傳統 BASIC 式程式語言印出文字時有個嚴重缺點,就是每印一次就必須跳一列。這樣的性能,不能滿足用來印出一個九九乘法表的要求。我創作的一個叫做 RUN 的指令,能解決這種問題,緊隨 RUN 之後,可以直接使用執行完畢後不會影響堆疊狀態之所有的 Forth 指令。使用方式,就如上述。
:
這個 RUN 指令在 Charles H. Moore 提出的原始設計中沒有,是我獨創的。它可以將 BASIC 程式語法大大的強化,等同於讓使用者在 BASIC 的使用環境中,能夠叫用所有的 Forth 程式。這個程式光用 BASIC 中的 PRINT 指令,不能辦到印出九九乘法表所要求的結果,只能用我創作的 RUN 指令,配合 Forth 系統原本的性質來完成。Forth 處裡輸出與輸入的能力很強,才容易做控制工作,其 I/O 性能是廣義性的,不只是文、數字在螢幕上的 I/O,也包括電腦在實體訊號方面的 I/O。這個程式的展示,只是個小範例。
:
外國人用12x12乘法表,我們中國人才用9x9乘法表。
:
第(08)個程式規模較大,是個完整的設計,主題是:根據年、月,或單只根據年,印出月曆。
所有的電腦都得處理日期與時間,電腦內的日期與時間資料影響很多事情,嚴重的,可以令電腦跑不起來。例如:以前,硬體組態設定 CMOS 所需的電力不足時,這個資料就會歸原成出廠時的原始時間,就不能上網,我曾經吃過這種虧,只好每次開機時,人工調整出正確的日期與時間才上網。
我試了三套不同作業系統的電腦,都能印出月曆,在顯示的動態時間訊息上,用滑鼠點一下就行,操作摘要如下:
W10 --> click right down corner
Ubuntu --> click right upper corner
Mac --> click right upper corner
在命令提示字元全黑視窗(command console)中,都可以下這個命令得到時間,寫程式需要時,可用來叫用: $ date
顯然,這麼重要的事情,電腦內必須有軟體來處理,還必須被設計成可以被公眾方便叫用的程式。因此,很多人寫這種相關程式來當作範例教材。Win32Forth 系統中 Demos 資料夾內,就有一個叫作 CalendarDemo 的範例,它採用物件導向程式(OO,Object-Oriented programming)的設計方式,印出一個半年曆。您可以自己打開該檔案來看看,我保證您看不懂程式寫法與程式中的叫用方法。OO 程式方式已經被淘汰了,以前與以後的慣常 Forth ,都不會具有這種功能,無法傳承,所以,您也別浪費時間去研究,我會用,但不想浪費時間介紹。
這個範例程式的執行原理,是根據一個簡化後的儒略日(julian day)計算程式,只處理1950年以後的日期,算出某個月份第一天的儒略日數值。根據這個數值,就能決定這一天必定是星期幾?再配合另外的指令,可以算出該年是否為閏年?該月有幾天?就能印出月曆。想想看,如果不給你程式,只給你這三個參數,您是否也能手寫出月曆?若是,這個程式也就是根據這個道理印出月曆的。關於儒略日這個術語,後面還有精確的算法範例程式,會再解釋,這裡就不討論,暫時只要知道,它能用來算出某年某月某日必定是星期幾,就夠了。
應用時,程式最後,提示了使用方法。
細研時,程式後面的說明很健全,每個指令執行前後堆疊上數據的變化,都有詳細的敘述。
學習時,請看看如何寫程式?學會怎樣才能把程式寫得清楚明白?日後便於追查、校正、再利用。程式中也有 CASE ----- ENDCASE 多重分支的標準用法,可以學習。
想摸通輸出月曆格式印出來的設計方法,就得實際去操作,才能看到結果。
:
\ Example 8. Calendars
VARIABLE JULIAN ( Julian date of 1st of a year, from Jan. 1, 1950)
VARIABLE LEAP ( 1 for a leap year, 0 otherwise. )
1461 CONSTANT 4YEARS ( number of days in 4 years )
: YEAR ( YEAR --, compute Julian date and leap year )
1949 - 4YEARS 4 */MOD ( days since 1/1/1949 )
365 - JULIAN ! ( 0 for 1/1/1950 )
3 = ( modulus 3 for a leap year )
IF 1 LEAP ! ( leap year )
ELSE 0 LEAP ! ( normal year )
THEN ;
: FIRST ( MONTH -- 1ST, 1st of a month from Jan. 1 )
DUP 1 =
IF DROP 0 ( 0 for Jan. 1 )
ELSE DUP 2 =
IF DROP 31 ( 31 for Feb. 1 )
ELSE DUP 3 =
IF DROP 59 LEAP @ + ( 59/60 for Mar. 1 )
ELSE 4 - 30624 1000 */
90 + LEAP @ + ( Apr. 1 to Dec. 1 )
THEN
THEN
THEN
;
: DAY ( DD MM YYYY -- JULIAN-DAY )
YEAR ( Compute JULIAN and LEAP)
FIRST + 1- ( add DD to 1st of the month )
JULIAN @ + ( add to Jan. 1 of the year )
;
: STARS 0 DO 42 EMIT LOOP ; ( form the boarder )
: header ( n -- ) ( print title bar )
cr cr 26 stars space
case 1 of ." January " endof
2 of ." February " endof
3 of ." March " endof
4 of ." April " endof
5 of ." May " endof
6 of ." June " endof
7 of ." July " endof
8 of ." August " endof
9 of ." September" endof
10 of ." October " endof
11 of ." November " endof
12 of ." December " endof
DROP
endcase
space 27 stars cr cr
." SUN MON TUE WED THU FRI SAT"
cr cr ( print weekdays )
;
: BLANKS ( MONTH -- ) ( skip days not in this month )
FIRST JULIAN @ + ( Julian date of 1st of month )
7 MOD 8 * SPACES ; ( skip columns if not Sunday )
: .DAYS ( MONTH -- ) ( print days in a month )
DUP FIRST ( days of 1st this month )
SWAP 1 + FIRST ( days of 1st next month )
OVER - 0 ( loop to print the days )
DO I OVER +
JULIAN @ + 7 MOD ( which day in the week? )
0= IF CR THEN ( start a new line if Sunday )
I 1 + 8 U.R ( print day in 8 column field )
LOOP
DROP ; ( discard 1st day in the month )
: MONTH ( N -- ) ( print a month calendar )
DUP
HEADER DUP BLANKS ( print header )
.DAYS ; ( print days )
: CALENDAR ( YEAR --- ) ( print year calendar )
YEAR ( compute JULIAN and LEAP )
13 1 DO I MONTH LOOP ( print 12 month calendars )
CR CR 64 STARS ; ( print last boarder )
\s
Usage:
yyyy YEAR mm MONTH
or
yyyy CALENDAR
第(09)個程式,是一個可以用來精算出任何年代之儒略日的程式。
前一個範例程式,用來計算儒略日的設計方法,只適用於 1900 年以後的日期,算起來就能比較快。
這個程式,則適用於人類信史以後,所有的年代與日期,計算量較多。
安排百例的原則,本應以簡短精要為主,前一程式稍長,但因事涉一個完整的典型應用,所以,我仍採用。這樣安排百例,不是隨興之所致而編排,其來有自。展示了前例,若還有牽扯,就接續產生下一個相關範例。前十個範例,仍然是以傳統的 Forth 程式格式為主,我才能借助於這樣的前引,導入我們的正軌,後面才引介 ABC FORTH 。
我接觸『儒略日』術語的日期,是早在民國 62 年(1973)我唸大三時。那時,我們只能寫 FORTRAN 程式,一學期只允許跑 11 次 mainframe 大電腦。終端機管理員,是學校剛畢業的應數系學長,很兇,要是沒有跟他們搞好關係,想要獲得一份電腦印出來、帶有蒙娜麗莎微笑之年曆,那就別想。於是,我就花時間了解曆法,研究月曆的產生方式,結果,從天文曆書上找到了答案,知道何謂儒略日?知道憑它印出月曆的規矩。天文學與曆法學對我們在校的學習成績沒有幫助,不會有助於畢業後按成績的分發,我只是有興趣把這個電腦內一定要用到的基本程式搞清楚。我從 1973 年的 Astronomical Almanac 學到了這麼一句話:『儒略日數加 1,以 7 除之,餘數即星期順序。』這本書,現在還在。我不想浪費篇幅介紹何謂儒略日?有興趣,請自己查一下 Google,就可以得到答案。我在今天的貼文中,告訴您相關於 Forth 資料的來源,反而比較重要。
網上的 Forth 科學程式庫中,第 22 個貢獻,就是計算儒略日的程式。網頁是:
Julian day in FSL source:
The Forth Scientific Library Project
https://www.taygeta.com/fsl/sciforth.html
||
||
\||/
\/
22. Conversion between calendar date and Julian day
https://www.taygeta.com/fsl/scilib.html
從源程式資料中,您可以看出,簡化算法與精確算法,兩種都有。簡化只需利用精確算法,得到 1900.01.01 的日數,以其重新做為計算基準,就能改採較為簡單的固定參數來簡化計算。簡化相關參數的 ABC FORTH 程式寫法,放在第(16)個程式內當範例。
與日期、時間相關的程式,在電腦系統中都很重要,除了搞月曆的日期,還有搞時間顯示的程式,我都能自己設計,搞程式設計的人就該學會這門技術。APPPLE II 的時代,我曾為台灣中壢工業區的力固磅秤公司,以 6522 晶片產生標準時基,設計一整天都能夠產生的精確時間,並將其設計成可被叫用的程式,他們要用它來管制卡車進出貨時的記錄,需要精確時間。我辦到了,賺到了一台謝崧梅贈送的撞擊式印表機。告訴大家這個故事,說明了程式技術是有價值的東西,請好好珍惜。如果有一天,我們也用 Forth 設計出作業系統,這些程式技術都得用上。
:
\ Example 9. The true Julian date
: JULIAN-DATE ( DD MM YYYY -- d, Julian date as a double integer )
>R ( save YYYY on return stack)
DUP 9 + 12 / ( 0 for Jan/Feb, 1 for others)
R@ + 7 * 4 / NEGATE ( take 1.75 days out for each year)
( 365.25=367-1.75 )
OVER 9 + 12 / NEGATE
R@ +
100 / 1 + 3 * 4 / - ( leap days generated by centuries)
SWAP 275 9 */ ( days in year before this month)
+ + ( add DD, days in year and misc)
S>D 1.721029 D+ ( add Julian date of Jan 1, 0 AD)
367 R> UM* D+ ( add days of prior years )
;
: whatday ( dd mm yyyy -- )
julian-date d>s 1+ 7 mod . ;
\s
Usage:
dd mm yyyy whatday
例:
1 1 2013 whatday 2
第(10)個程式,以整數表示法算出數學函數 sin 及 cos 。
這個程式原本是 16 位元時代的產物,已經不適合現代,但仍有存在於單板微處理器上的價值,所以留作範例。
16 位元的時代,最大單整數只能表示到 65535 , 32 位元則可以有 10 位數, 64 位元可以有 18 位數。
所以, 8 位元的時代,微處理器上,不適合搞浮點運算,16 位元則勉強尚可,4 位有效數字仍有用途。
32 位元以後,才是比較可以正式使用浮點運算的恰當環境,已經可以有 10 位有效數字, 64 位元就更不用說了。
我熟悉浮點系統中基本函數的設計方法,學過,也試用過無數種算法,知道各種算法之優劣,包括只用硬體數學運算處理器來獲得函數的方法,也非常熟悉。所以,我設計的 ABC FORTH 系統中都具有浮點運算的全套功能。今天可以使用這些浮點性能分析問題,但只用來討論此第(10)個程式的設計內容。
最有效率的 sin 與 cos 函數值產生方法,是使用泰勒(Taylor)級數展開式的算法。我先把式子列在這裡:
sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ….
cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ….
算法學上,強調效率,上列表示式必須改成計算次數少、能夠算得快、不會造成溢位的多括弧表示式。若只取四項,可以寫成:
sin(x) = x(1-x^2/6(1-6x^2/120(1-120x^2/5040(1-5040x^2/362880))))
cos(x) = (1-x^2/2(1-2x^2/24(1-24x^2/720(1-720x^/40320))))
上列表示式,就是用來設計這兩個函數的根據式子。
在整數系統中沒有小數點,函數值只好以放大位數的方式表示,在 16 位元中的最大整數為 65535 ,為了能夠健全顯示所有的數值,只能安排少掉一位數的方式表示,結果,數字只適合放大一萬(myriad)倍,所以,程式中有一個宣告產生的常數叫做 10K 。放大一萬倍後,函數值只剩四位數有效值。而輸入的格式,被設計成採用 0 到 90 度數的方式,例如:角度為 37 度,就使用『 37 sin . 』的方式獲得 6017 的結果,表示所得的函數值是 0.6017 。您可以用硬體數學計算處理器測試答案,應該是:
37.0e0 FPI F* 180.0e0 F/ FSIN F. .601815
我的 ABC FORTH 提供了一個更方便的計算器(calculator)式的使用法,可以直接執行出下列結果,不妨自行試試看:
REXP sin ( 37 * FPI / 180 )
RANS .601815
換句話說,這個程式,使用起來沒有問題,誤差只有萬分之一。
程式中,執行 (sin) 時,用到幾個常數,它們的來源,是上列式子中各個係數的倒數,例如:72、42、20、6 就是上列 sin(x) 表示式中常數部分之倒數:
72=362880/5040
42=5040/120
20=120/6
6=6/1
同理,可以類推出執行 (cos) 時所需用到的幾個常數。
只有 4 位有效位數的函數值有什麼用?還很有用。
最簡單的舉例,就是直接用它設計螢幕上的繪圖,螢幕的解析度一般只有 1024 點左右,萬分之一的精確度很夠用了,繪製動態展示圖時,用這種純粹單整數的函數算法計算時,可以很省時間,產品就可以具有快速的動感。
以前,我們也用這種方法設計自動焊接機器的定位驅動,由於算得很快,驅動效果非凡,燒焊弧線,一點問題也沒有。
我搞工程,熟知工程界把千分之一的精確度叫做一條,算是很精準。以前,德國人賣的工作母機配上光學尺,生產時,才能有這樣的精確度。一般工廠中使用的儀錶,精確度平均都只有 5% ,好錶的精確度也只有 3% 。我任公職退休前管理過的原子爐中,只有一個超級精確的重水液位顯示儀器,可以有 1% 的精確度。
這樣的解釋,告訴大家,萬分之一,很準了。
:
\ Example 10. Sines and Cosines
31415 CONSTANT PI
10000 CONSTANT 10K ( scaling constant )
VARIABLE XS ( square of scaled angle )
: KN ( n1 n2 -- n3, n3=10000-n1*x*x/n2 where x is the angle )
XS @ SWAP / ( x*x/n2 )
NEGATE 10K */ ( -n1*x*x/n2 )
10K + ( 10000-n1*x*x/n2 )
;
: (SIN) ( x -- sine*10K, x in radian*10K )
DUP DUP 10K */ ( x*x scaled by 10K )
XS ! ( save it in XS )
10K 72 KN ( last term )
42 KN 20 KN 6 KN ( terms 3, 2, and 1 )
10K */ ( times x )
;
: (COS) ( x -- cosine*10K, x in radian*10K )
DUP 10K */ XS ! ( compute and save x*x )
10K 56 KN 30 KN 12 KN 2 KN ( serial expansion )
;
: SIN ( degree -- sine*10K )
PI 180 */ ( convert to radian )
(SIN) ( compute sine )
;
: COS ( degree -- cosine*10K )
PI 180 */
(COS)
;
\s
Usage:
To test the routines, type:
90 SIN . ( 9999 )
45 SIN . ( 7070 )
30 SIN . ( 5000 )
0 SIN . ( 0 )
90 COS . ( 0 )
45 COS . ( 7071 )
0 COS . ( 10000 )
沒有留言:
張貼留言