2024年10月6日 星期日

一百個例題 (96 ~ 100)


曾慶潭 Ching-Tang Tseng
ilikeforth@gmail.com
Hamilton, New Zealand
7 October 2024

第(96)個範例,是一個只能在 Lina64 中執行,可以將網址內原為中文格式碼的中文文字顯示出來的程式。

由於在 W10 環境中,文字視窗內中文文字的顯示方式與 Ubuntu 作業系統中者不同,所以,不要在 W10 環境中使用這個程式。

在 Win32Forth 續用機會逐漸接近尾聲的時候,為它重新開發這個主題的程式,意義不大,此處只能盡量解釋程式的精神所在。

程式後面附貼了 20241007 在 Ubuntu V20.04 啟動 Lina64 實際載入並執行這個程式後的顯示結果,讓大家體會程式的實際用處。

我每天使用電腦來做許多事情,瀏覽網路時,收集無數的訊息,影、音、圖、字通通都有。除了訊息,為了便於回查資料出處,一定要把網路的地址也同時留下來。又由於國際各國文字編碼格式的不同,如果網址中使用了非英文的文字,每當操作滑鼠進行反白、複製取得網址,再到文字檔案中去貼上,儲存這個網址時,就會出現所謂 UTF-8 編碼與 BIG-5 編碼之不同的問題,而令取得的網址出現很多以 『 % 』 號前引,後面跟著十六進制碼的東西,無法讓人直接看懂。而且,在 W10 環境擷取這種網址存進檔案,再轉往 Linux 環境打開檔案來看時,就只能顯示這類編碼,想看正確的結果,都得經過字型編碼的規格轉換。當然,系統中有可以自動把整個檔案內容翻譯出來的指令可用,翻譯完就不能再回頭給原作業系統使用了。我很少這樣做,每次,也只有單一個網址的需求問題。

這樣擷取的網址資料,經過 20 幾年之後,留下來的有用網址,已經是成千上萬個了。晚期的網址,才會有這樣的問題,早期的,都是只有英文。現在,每當想要回查網址訊息時,就常常會遭遇到因尾綴 UTF-8 字型編碼,而無法直接看出網頁實際名稱的狀況。

我用 Forth 寫程式,處理所有我會遇到的問題,上述狀況,也是在改用 64 位元之 Linux OS 環境後,才出現的困擾,所以就設計了這個程式。實際上,在 32 位元的 Win32Forth 系統中,也能設計得出具有同樣功能的程式,程式的難度,只屬於二級的程度而已。處理關鍵,只在設法進行刪除所有的 % 符號,轉換出來的結果,在純文字視窗上,就能顯示出原來的中文字了。 % 符號,等於只是一個前引的格式碼,沒有其他的意義。

關於字串處理方面的程式,我在百例中的介紹並不很多,原因就是它們都不屬於數學計算的範疇。我在設計應用程式時,仍然常需面對字串處裡問題。可是, Forth 標準指令中,並沒有許多現成的字串處理指令可用,例如字串的存取、裁剪、比較、增刪 ..... 等等,都沒有現成指令可用,必須自己設計。我並不喜歡討論這方面的技術,因為換個系統之後,一切都得重來,為了適應新環境,重新設計程式都很耗時間,除非是為了非做不可的事情,我才會去仔細研究。

我在 20181202 貼出過關於字串處裡的基礎教學文章:字串。隨後,國際論壇上大概也因此而大肆討論了許久,最後,好像就如我所說的,許多指令雖很有用,但公認不必列為標準,會用的人,兩三行就能自行設計得出來,不熟 Forth 者仍然不會。自此以後,也不再有熱心的人士,願意像我這樣,大耗篇幅寫例題程式了。

 
\ (96) url translator 20180710

0 value ptr
create url 32 cells allot
create web 32 cells allot

: reset$buf
  url 32 cells erase
  web 32 cells erase
  cr ." Input url string $ " cr
  input$ url $! ;

: setptr
  url $@ drop to ptr ;
  
: url>web
  begin
     ptr c@ 0 <>
  while
     ptr c@ [char] % =
         if   HEX  0. ptr 1+ 2 >number 2drop d>s web $c+ DECIMAL 3 +to ptr
         else ptr c@    web $c+   1 +to ptr 
         then
  repeat ; 

: show
  cr ." url = " url $@ type cr
  cr ." web = " web $@ type cr ;

: main 
  reset$buf
  setptr
  url>web 
  show ;  
  
main
\s
ching@ctt:~$ ./l

AMDX86 ciforth 5.3.0 
fload 96.f

Input url string $ 
http://www.teepr.com/376386/lexychang/%E4%B8%B9%E9%BA%A5%E5%A4%A9%E4%BD%BF%E7%9A%84%E7%B4%A0%E9%A1%8F%E7%85%A7%E6%9C%83%E8%AE%93%E4%BD%A0%E7%9F%A5%E9%81%93%E4%BB%80%E9%BA%BC%E6%98%AF%E3%80%8C%E7%9C%9F%E6%AD%A3%E7%9A%84%E4%BB%99%E5%A5%B3/

url = http://www.teepr.com/376386/lexychang/%E4%B8%B9%E9%BA%A5%E5%A4%A9%E4%BD%BF%E7%9A%84%E7%B4%A0%E9%A1%8F%E7%85%A7%E6%9C%83%E8%AE%93%E4%BD%A0%E7%9F%A5%E9%81%93%E4%BB%80%E9%BA%BC%E6%98%AF%E3%80%8C%E7%9C%9F%E6%AD%A3%E7%9A%84%E4%BB%99%E5%A5%B3/

web = http://www.teepr.com/376386/lexychang/丹麥天使的素顏照會讓你知道什麼是「真正的仙女/
 OK

第(97)個範例程式是用來測試 Lina64 系統中的矩陣及亂數函數應用的結果,兩項成果顯示都有很好的性能。

關於這兩大項的測試,我分開來解釋,並附上實際執行後的測試輸出結果,便於說明。

關於矩陣的測試,只偏重於測出帶 0 指標的單元,是否已被正常的納入系統?用不用都無所謂,輸出時就必須顯示它們的存在。這一特點,在原本的 Win32Forth ABC Forth 系統中沒有刻意安排。我是在使用許多大型數學計算套裝軟體後,才體會出必須令其存在的,否則會有很多現成的程式,在抄用時會遭遇很大的困擾,必須人工自行調整指標來對應,不如就在建系統時加入此一功能,會方便許多。當然,每次宣告陣列、矩陣來用時,就會損失一些可能用不上的記憶體。今日世界,這已不再是問題,那就不要省,對我而言,加設計也只是舉手之勞。這是這個範例測試出設計成功的一個項目。宣告後,沒用到的具有 0 指標之單元,內容全為 0 ,這就是成功的結果。

其次,關於亂數,分成三大項來看結果,我設計系統時沒有前人的文獻可以參考,自己設計後,使用上的『協定』也只好自己規定了。

所有的亂數都是炒作整數亂數後延生出來的,炒作方法就是把位元花樣向左、向右移動幾個質數次,再與原花樣進行 XOR 運算,保證與原花樣完全不一樣,這樣搞個幾次,就成了新的亂數,下一個照樣畫葫蘆。起始花樣,則藉 CPU 旁邊會跑個不停的計時器內容之下半段位元花樣來亂化,使用這種軟、硬通吃的技術,效果就會很好。

這樣產生的整數亂數,因用到全部的位元來生亂,最高位元當然也被炒到了,程式才容易設計,所以,直接使用時,會有負值出現,加一個組合語言指令 ABS 就能解決問題,亂數產生的速度非常快,是 giga 分之幾秒就能完成。但我認為負的亂數也很有用,功能就保留下來了,用時自己注意。

我的浮點數是全用整數四則運算完成的十進制浮點,拿整數亂數直接用,當然就更方便。只是用時要注意,一般的基本用法,都是先取得只介於 0 至 1 之間的浮點亂數,想放大多少,自己乘上去後才使用,就得了。

我的複數系統是一般數學運算套裝軟體系統上不太具有的東西,所以就不用找規格了,使用『協定』也只好自己創立,這個範例中有簡單的使用規格,叫用亂數函數時,使用者提供的參數就是新產生之亂數的上限範圍,實數的部分可以與虛數的部分不一樣,例如此範例中,實數上限是 100 ,虛數上限是 10 ,測試結果,顯示都很好,非常恰當。

 
\ (97 random number test

\ beware! In integer mode, need a NEGATE operation to get negative random number
: NTEST 10 0 DO CR 10 0 DO 100 RAND negate 4 .R LOOP LOOP ; 
: FTEST 10 0 DO CR -1.0 E 2 FRAND F. LOOP ;
\ beware! upper bound input format ignore + and ii
: ZTEST 10 0 DO CR -1.0 E 2 1.0 E 1 ZRAND Z. LOOP ;

3 integers i j k
complex z0
3 3 3 [tensor] zzz

: init 
  basic
10 let z{ z0 = 0.0 E 0 + 0.0 E 0 ii }z
20 for i = 1 to 3
30 for j = 1 to 3
40 for k = 1 to 3
\ attention! this is the usage to create a complex random number
50 let z{ zzz ( i j k ) = RND ( 1.0 E 2 - 1.0 E 1 ii ) }z
60 next k
70 next j
80 next i
90 end ;

: MAIN 
  randomize
  ntest cr ftest cr ztest cr 
  init
  basic
10 for i = 0 to 3
20 for j = 0 to 3
30 for k = 0 to 3
40 print " zzz( " ; i ; j ; k ; " )= " ; z{ zzz ( i j k ) }z
50 let z{ z0 = z0 + zzz ( i j k ) }z
60 next k
70 next j
80 next i
90 print cr cr " z0 = " ; z{ z0 }z
100 end 
  cr ." --- The end of testing --- "
;

\s
ching@center:~$ cd lina64
ching@center:~/lina64$ ./f5124

AMDX86 ciforth 5.3.0 
fload 97
I : ISN'T UNIQUE                                                
J : ISN'T UNIQUE                                                
 OK
main

 -10  -8 -72 -61 -31 -86 -14  -7 -86 -20
 -42 -94 -92 -27 -86 -45 -57 -77 -34  -4
 -98 -89 -61 -28 -86 -66 -36 -71 -68 -71
 -35 -10 -10 -84 -85 -82 -23 -14 -89 -20
 -34 -51 -10 -87 -58 -67 -21 -64 -43 -51
 -26 -18 -63 -99 -30 -22 -34 -92 -25 -78
 -58 -58 -98 -64 -93 -54 -23 -43 -99 -38
 -83 -68 -50 -68 -11 -74 -44 -49 -85 -42
 -52 -18 -23 -93 -39 -41 -37 -16 -49 -67
 -40 -39 -66 -99 -70 -45 -99 -16 -17 -28

-1.913628913
-89.96769746
-90.80277685
-93.25782136
-12.5438047
-68.49962742
-8.071056155
-61.09243441
-47.21671679
-32.33685587

-3.989974468+ 4.9740950527ii 
-77.58595045+ 0.7664840648ii 
-42.15458337+ 0.0229446231ii 
-5.083676585+ 6.5485581007ii 
-62.74442857+ 5.2724854219ii 
-70.1131553+ 6.2933488809ii 
-37.53475328+ 6.8368182786ii 
-38.22238984+ 1.2874766663ii 
-96.85347849+ 4.7545419669ii 
-3.281314505+ 5.6388045221ii 

zzz( 0 0 0 )= 0 
zzz( 0 0 1 )= 0 
zzz( 0 0 2 )= 0 
zzz( 0 0 3 )= 0 
zzz( 0 1 0 )= 0 
zzz( 0 1 1 )= 0 
zzz( 0 1 2 )= 0 
zzz( 0 1 3 )= 0 
zzz( 0 2 0 )= 0 
zzz( 0 2 1 )= 0 
zzz( 0 2 2 )= 0 
zzz( 0 2 3 )= 0 
zzz( 0 3 0 )= 0 
zzz( 0 3 1 )= 0 
zzz( 0 3 2 )= 0 
zzz( 0 3 3 )= 0 
zzz( 1 0 0 )= 0 
zzz( 1 0 1 )= 0 
zzz( 1 0 2 )= 0 
zzz( 1 0 3 )= 0 
zzz( 1 1 0 )= 0 
zzz( 1 1 1 )= 16.138346694- 1.6922562207ii 
zzz( 1 1 2 )= 11.673369547- 0.0886917048ii 
zzz( 1 1 3 )= 12.744789187- 3.7724680765ii 
zzz( 1 2 0 )= 0 
zzz( 1 2 1 )= 31.962691299- 1.3816571182ii 
zzz( 1 2 2 )= 40.44457283- 1.5831628868ii 
zzz( 1 2 3 )= 20.592126444- 4.1070317085ii 
zzz( 1 3 0 )= 0 
zzz( 1 3 1 )= 5.7211277157- 1.9009267784ii 
zzz( 1 3 2 )= 79.010045666- 1.7076510055ii 
zzz( 1 3 3 )= 36.175071964- 1.1219479774ii 
zzz( 2 0 0 )= 0 
zzz( 2 0 1 )= 0 
zzz( 2 0 2 )= 0 
zzz( 2 0 3 )= 0 
zzz( 2 1 0 )= 0 
zzz( 2 1 1 )= 15.89740986- 2.4554373865ii 
zzz( 2 1 2 )= 82.495140485- 8.5362387692ii 
zzz( 2 1 3 )= 17.291546835- 8.3884016077ii 
zzz( 2 2 0 )= 0 
zzz( 2 2 1 )= 15.663485123- 8.9183735609ii 
zzz( 2 2 2 )= 87.857473069- 2.1572277506ii 
zzz( 2 2 3 )= 8.0974462118- 4.8481376385ii 
zzz( 2 3 0 )= 0 
zzz( 2 3 1 )= 16.89172751- 5.8226671362ii 
zzz( 2 3 2 )= 67.783749813- 1.849634518ii 
zzz( 2 3 3 )= 57.98394664- 6.3832837116ii 
zzz( 3 0 0 )= 0 
zzz( 3 0 1 )= 0 
zzz( 3 0 2 )= 0 
zzz( 3 0 3 )= 0 
zzz( 3 1 0 )= 0 
zzz( 3 1 1 )= 5.9138480523- 6.632469444ii 
zzz( 3 1 2 )= 92.038071469- 2.2556417577ii 
zzz( 3 1 3 )= 18.729357865- 7.4502652215ii 
zzz( 3 2 0 )= 0 
zzz( 3 2 1 )= 67.299863245- 2.0522561644ii 
zzz( 3 2 2 )= 80.622464446- 2.3218369173ii 
zzz( 3 2 3 )= 74.024361093- 3.2010649041ii 
zzz( 3 3 0 )= 0 
zzz( 3 3 1 )= 95.399681736- 5.8407040896ii 
zzz( 3 3 2 )= 59.354835252- 6.4971174443ii 
zzz( 3 3 3 )= 12.681339537- 9.1802079012ii 


z0 = 1130.4878896- 112.1467594ii 
--- The end of testing ---  OK

第(98)個範例程式是用來解決一個根據巴斯卡(Pascal)三角塔原理為基礎所演變出來的問題,讓程式可以進行矩陣運算,也讓結果易於被核驗。

高階二項式 (a+b)**n 的展開式,每一項的係數所形成的固定變化,曾被一位法國的數學家叫布萊斯‧巴斯卡(Blaise Pascal,1623年6月19日-1662年8月19日),妥善整理出來,是一個漂亮的三角塔式的係數數列,故得其名。但實際上,對這個問題有過研究的古老文獻,非常的多,顯然,發現人並不是巴斯卡,甚至於中國人也有,這方面,就不用去考究了,我們直稱其為巴斯卡三角塔便可。

巴斯卡三角塔並不容易被電腦程式漂亮的印出來,為了方便,只適合把三角塔印成上三角矩陣,或者是下三角矩陣。因此,這個程式的題目,就要求計算出把這兩種矩陣加起來後的結果到底為何?

我控留此程式的目的,主要是因為這樣的題目能磨練程式的寫法,另外,也是個能夠用來驗證矩陣的資料結構設計得是否正確之題目。算是一個優良的測試材料。關於磨練程式設計技術的部份,請自行看看程式本身,就能了解。關於驗證資料結構是否正確的問題,則還能進一步探討。

巴斯卡三角塔的整齊結構,易於只用肉眼觀察,就能看出對錯。兩個矩陣相加後,因為元素內容的花樣,係與對角線對稱,也易於只用肉眼觀察,就能看出對錯。一般而言,我們使用的電腦,宣告出 20X20 的矩陣,螢幕上尚能印出不需跳列的輸出,能測試到這樣的尺度,大約也足夠表示設計無誤了。

所謂的進一步探討,並不絕對需要,但對矩陣運算系統的設計與發展而言,就很有必要。大家可以看看程式的寫法,您能發現,就算要把矩陣印出來,也得在被印出程式之指令外面,先行宣告出矩陣的維度,才辦得到。後續那個矩陣的加法運算,亦然。這就表示,寫成的這套程式,代表著所有的矩陣運算程式都得這樣處理。

我在發展出許多數學計算系統後,自覺能夠改善矩陣體系運算系統的性能,能夠設計得出 M+ , M- , M* , M/ , Mx , M. , 1/M , Mt , M0 , Mneg ..... 等等,可以不管宣告維度是多少,就能直接使用的運算指令。這樣的性能,必須依靠矩陣在宣告產生時,就把維度也存在資料結構中才行,我辦到了這項設計,但沒有進行矩陣運算系統的發展,因我個人很少有接觸這方面問題的機會。

事實上,我還有許多的構想,可以設計出更多的數學運算體系,都很有意義。但自從進入 64 位元的環境後,面對的系統發展問題,不是更多的數學體系,而是更多的不同 Forth 基本平台。因此,發現研究的主要方向,自己必須嚴格把握住,才有發展的坦途可走。整個系統的基礎,若不專心設計得更好,發展再多的數學體系,只會弄亂自己的陣腳。於是,停止了矩陣體系運算系統的開發了。
Win32Forth 系統,可以順利的執行出這個程式。純粹只處理整數的數學問題,這類程式都能這樣被直接執行得出來。

 
\ (98) Pascal matrix generator

4 integers i j k n
20 20 (matrix) mm

: i-1 i 1- ;
: j-1 j 1- ;

: printmm
  basic
100 for i = 1 to n
110 for j = 1 to n 
120 let k = mm ( i j )
130 run k 8 .r space
140 next j
150 run cr
160 next i

200 end ;

: upm 
  basic
10 for j = 1 to n
20 let mm ( 1 j ) = 1
30 next j

40 for i = 2 to n
50 let mm ( i 1 ) = 0
60 for j = 2 to n
70 let mm ( i j ) = mm ( i-1 j-1 ) + mm ( i j-1 )
80 next j
90 next i

500 end ; 

: lpm 
  basic
10 for i = 1 to n
20 let mm ( i 1 ) = 1
30 next i

40 for j = 2 to n
50 let mm ( 1 j ) = 0
60 for i = 2 to n

70 let mm ( i j ) = mm ( i-1 j-1 ) + mm ( i-1 j )
80 next i
90 next j

500 end ;

: spm 
  basic
10 for i = 1 to n
20 let mm ( i 1 ) = 1 :: mm ( 1 i ) = 1
30 next i

40 for j = 2 to n
50 for i = 2 to n
60 let mm ( i j ) = mm ( i j-1 ) + mm ( i-1 j )
70 next i
80 next j

90 end ;

: pascal ( n -- )
  [[ n ]] !
  basic
10 run cr upm
20 run cr printmm
30 run cr lpm 
40 run cr printmm
50 run cr spm
60 run cr printmm
70 end ;

page 5 pascal

第(99)個範例是在『Euler數學問題網頁』中,條列之一個問題的電腦程式解法。

問題問:直角三角形的邊長為 1000 ,請問三邊邊長均為整數時的長度各為多少?

Euler’s 數學問題網頁,是一個最近兩年才聞名起來的網頁,所出問題,大部分是需要能夠運算大數目字的系統才能解答的題目,現已大約收集了六百多個問題,貼出在網上,讓人隨意發揮。剛開始時,網頁出入管制,沒有那麼嚴格,我有空時,就會去拿題目,試一試自己的系統,那時,大約我都能解,解題時,只覺得必須全靠電腦,很難有單憑思考就能解析出來的題目。後來,該網頁開始進行管制了,必須登記身份才能使用,我就放棄了追蹤。

話雖如此,那種數學題目,反而代表了現今網路環境熱門話題的演變趨勢,都屬於同一類型的問題。例如,要您從海量資料中,比對出合乎指定條件的網上訊息,您就得像處理無限位數的大數那樣,把一小節數字用電腦找出來。所以,這個數學問題網頁的建立,是一個時代的產物。以後,如果您有興趣也有本領解這種題目,就可以根據名稱,自行前往該網頁進行探索。我不參與,所以也沒留網址,但很容易搜索得到。

我留下這個程式的目的,除了題目優良外,還有個簡單意義,就是畢氏定理的運用,能讓我寫成的程式,比別種程式語言的語法更為清楚、簡單。本來,問題的表面只有ㄧ個條件,就是 a+b+c=1000 ,其中,有兩個獨立變數,一個因變數,條件只有一個時,沒辦法有特解,只能有也附帶條件式的通解。如果學過了數學上的畢氏定理,您就知道另有一個很好的輔助條件可以利用,也就是斜邊的平方必須等於另兩個直角邊之各自平方的和,亦即 a^2+b^2=c^2 。

這麼一來,寫程式時,語法好的系統,就很容易讓人看懂,執行起來,就不容易出差錯, BASIC 程式語言的語法,就具有這種優點。仔細看這不到十列的程式,使用兩個環路、一個邏輯判斷來完成,兩個環路就是給兩個獨立變數用的,範圍就指定在 1 到 1000 之間。邏輯判斷就只給畢氏定理使用,以取得因變數之值,合條件者就是答案,不合條件者就不是答案。這樣的電腦解法,讓普通人純用手算,就會成為不切實際的解題方法,用電腦算,又快又準,立刻就能得到答案。此程式,只處理整數,就可以在我設計的系統中執行得出來,我就不在此處提供解答,印出來的答案一定對,您可以自行核算。

本來, Forth 系統的本質,就是一種具有延伸性的程式語言,我沒附加 BASIC 式語法的執行能力前,早就有名家為 Win32Forth 系統添加了組合語言的功能,只是後來大家都不太使用了,所以罕有人再強調。但是,這套 Win32Forth 中的 Assembler 非常有名,許多專門介紹組合語言的網頁中,竟然也把這套組合語言列為一套正式的特產,性能既好也完整。

事實上,我自創添加進去的 BASIC 式語法,等同於 Forth 系統中的 Assembler ,就是 Forth 中的 BASIC 。

一旦一套 Forth 系統,同時具有以他種語言之語法執行出結果的功能時,這個 Forth 系統,就也具有能夠在語法中互相來去、交流穿插的功能。這也就是為什麼我能利用這種精神,在 BASIC 語法中創建出一個 RUN 指令的原因。我把雙方語言的特質都搞清楚了,賦予這種好處,就不是難事。這樣的發揮, Forth 系統的發明人並沒有善加利用。實際上,他有點痛恨 BASIC 在當年霸佔市場的強勢狀態,那時,他好像是只為了要藐視一下 BASIC ,才發表了他的那篇 Tiny BASIC Compiler 論文。我,則是取長補短,設計出了自己的東西。

另外, Charles H. Moore 終生都痛恨浮點數運算,所以他也從來不寫浮點數運算方面的程式。這一點,我個人認為不對,所以,我自創系統中所有的浮點數計算功能,如何實現?都是我自己安排、自己實現的設計。當然,仍屬於 Forth 浮點範疇的部份,只要能師法於現成的成果,我就取用,如果沒有,我就自行設計,現在也已很熟了。

我仍然是站在巨人的肩膀上創作系統,自己的成就只有一小部份。

 
(99)euler9
\ Subject: Project Euler Problem 9 : unique Pythagorean triple summing to 1000 
\ a+b+c=1000

3 integers a b c

: main
  basic
10 for a = 1 to 1000
20 for b = a to 1000
30 let c = 1000 - a - b
40 if ( a * a + b * b ) = ( c * c ) then 60
50 goto 70
60 print a , b , c
70 next b
80 next a 
90 end
;

第(100)個範例,是一個能夠操作出讓單一個位元為 1 或為 0 的工具程式。

整台電腦中任一位址的內容,可以被取來放入 bitbuffer 變數內,然後才用這個程式中的操作指令,來令其中的單一個位元成為 1 或成為 0 。

操作的方法,在程式中有舉例,也只列出了一個顯示結果,要怎樣用才會更為方便?請自己加添設計出更簡單的指令,就能隨您所願。原本這個程式是在 64 位元環境中由 Bob Dickow 設計的,我試用後覺得比我自己設計的更好用,程式略作修改後,就被收集進這個範例了。

這種『位元操作』能力的展現,是一種 Forth 程式語言之特殊性能,別種程式語言都很難辦到這麼方便的設計格式,我用了 40 多年的 Forth ,只要想到要進行一些位元操作的工作時,深知非 Forth 莫屬。

我設計過一份以 Wina32 為平台,全由叫用硬體 co-processor 方式來完成所有浮點計算工作的系統。那個系統是標準 32 位元的設計,數據在 CPU 與 co-processor 間傳遞,執行所有的軟體指令都沒問題,硬體接線也與數據傳輸要求規格相符,所以,我很快便完成了設計,實際使用時,也很方便,很快就完成了 ABC Forth 的增建。

後來,我轉往 64 位元以 Lina64 為平台重新發展系統時,首先遭遇到的問題,就是從 CPU 內傳送 64 位元的單整數當 32 元的雙整數到 co-processor 後,因為硬體接線的格式與 32 位元者完全不同,我就不能再用 co-processor 設計浮點系統了。這問題不單純只是軟體指令的不能使用而已,硬體接線與數據傳輸時脈規格也完全不同,所以不能再寫組合語言實現同樣的夢想。除非能令系統回到 32 位元執行浮點運算,算完,再回到 64 位元系統顯示結果,這很麻煩,叫用 C 的庫存程式可以完成,但 gForth 系統就顯示了執行速度非常慢的問題,不值得這樣做。

我為什麼知道上述問題?就是因爲我使用了 Forth 的位元操作能力,仔細的測試過問題。把簡單位元花樣的數字,在 CPU 與 co-processor 間來回傳輸、簡單執行,再印出前後結果,一經比對,發現連一個簡單位元花樣的 1.2E0 浮點數,都沒辦法處裡得正確。無論以軟體設計了多少個委屈程式,都沒辦法獲得正確的結果。最後,只好放棄使用硬體,改成走自創純軟體設計之十進制浮點系統的發展方向,現已完成了設計。

這百例中,有許多範例都是我在發展系統期間所使用的工具程式,都有著深刻的印象與使用經驗,這些範例都不是幾天之內就能完成的研究,它們能用時,都是經過長久研發與各種測試步驟後,才得到的結果。

我貼完了百例,也對自己過去所做過的工作,進行了一次完整的複習,對我自己而言,很有益處。

 
\ (100)FORTH BIT Arrays , by Bob Dickow dickow@turbonet.com
\ version 1.0.3 Mar 2, 2018
\ Notes:
\ endian agnostic, address unit agnostic (untested)
\ Vocabulary (internal dependencies) ^2MASKS bmask _BIT@
\ Vocabulary: 2^ BIT@ BIT! BOOL@ BOOL!
\ BOOL@ and BOOL! are convenience words to avoid converting FALSE to 1
\ 2^ may be handy outside the array access words. Included for compatibility with earlier versions
\ Buffers for a bit array should be a minimum of 1 CELL in size

DECIMAL

CELL 8 * CONSTANT AddressUnit \ 8 is bits in a byte

\ calulates the power of 2 ( n -- 2^n )

: 2^   ( n -- 2^n )
  1 swap lshift
;

\ compiling word, creates an array of CELL * 8 powers of 2 for masking use in the array fetch and store words. 
\ does: ( nindex -- nvalue  ) \\ nindex is 0, 1, 2 ... giving 2^31, 2^30, 2^29 ... 2^0

: ^2MASKS  (  --  )
  CREATE
  0 AddressUnit  1-  DO 
   i 2^ 
   , -1 +LOOP  
  DOES>  SWAP CELLS + @ 
;

^2MASKS bmask \ lookup table of powers of 2 for the BIT@ and BOOL@ words

\ factored out, internal use by BIT@,  BOOL@

: _BIT@    ( caddr n-index -- n ) 
  AddressUnit /MOD 	( caddr n n )
  CELLS        ( caddr n n )
  ROT  +       ( n caddr )
  @ SWAP       ( nval n )
  bmask AND    ( n )
;


\ given a memory buffer address caddr and index n representing at bit-wise 
\ index, return the value of the bit at that index as 1 | 0.

: BIT@   ( caddr n-index -- n ) 
  _BIT@  ( n )
  0= NOT
  ABS
;

\ store a value TRUE|FALSE or 1|0, given the value (TRUE|FALSE or 1|0), an address, and a bit-wise index
\ (synonym for BOOL! below)

: BIT!   ( n-value caddr n-index -- )
  ROT ABS 1 AND        ( caddr n n ) \ bounds check
  IF [']  OR ELSE ['] XOR  THEN >R \ set masking op
  AddressUnit /MOD 		 ( caddr n n )
  CELLS                ( caddr n n )
  ROT  +  DUP          ( n caddr caddr )
  @ ROT                ( caddr caddr nval )
  bmask R> EXECUTE     ( caddr n )
  SWAP !
;


\ given a memory buffer address caddr and index n representing at bit-wise
\ offset, return or store the value of the bit as TRUE|FALSE values.

: BOOL@   ( caddr n-index -- flg )  
  _BIT@  			 ( n )
  0= NOT       ( n )
;

\  Synonym for BIT!
\  store a value TRUE|FALSE ( or 1|0 ), given the value n, a base address of the array, and a bit-wise index into it.

: BOOL!   ( n caddr n-index --  )  
  BIT!  
;


DECIMAL
\ create the buffer:
CREATE bitbuffer 2 CELLS ALLOT 

\ initialize buffer to all on bits:
\ 4294967295 DUP bitbuffer DUP ROT ROT ! CELL+ !
-1 DUP bitbuffer DUP ROT ROT ! CELL+ !

\ store a bit at position 34
0 bitbuffer  34 BIT!
0 bitbuffer  67 bit!
0 bitbuffer 127 bit!

\ for lina64
: bottomruler130 cr 
." 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 " cr
." 0=========1=========2=========3=========4=========5=========6=========7=========8=========9=========0=========1=========2=========3 " cr ;
: binary 2 base ! ;
: (du.) <# #s #> ;
: du.  (du.) type bottomruler130 ;

\ test it:
bitbuffer 2@ binary du. decimal \ displays: 1111111111111111111111111111111111011111111111111111111111111111

沒有留言: