2019年2月15日 星期五

測試函數

測試函數


Ching-Tang Tseng
Hamilton, New Zealand
16 February 2019
ilikeforth@gmail.com
http://forthfornight.blogspot.com



我自建浮點運算系統、自建函數,建成之後,首先碰到的問題,就是會問自己建的到底對不對?

測試函數就成了自建系統後的必要工作,否則日後的應用會連自己也未必相信計算結果確實可靠。有許多人討論過測試方法,甚至於提供測試程式,但我鮮少利用,道理無它,那些資源,怎麼看都覺得薄弱不堪、有所欠缺。

我自覺比較可靠的測試方法,就是應該簡化成:直接取得一序列的函數值,直接繪圖顯示結果,目視就能判斷函數的功能正確與否。

不幸,我們通常使用的 FORTH 系統,發展的初始階段,通常不具有能夠順利繪圖的功能,因此,不易順利達到這樣的測試目的。

我在進入 64 位元環境後,全面改用 Linux 作業系統,使用 Lina64 作為解決問題的主要工具。取得的原始系統,除了沒有浮點運算的功能外,也沒有繪圖的功能,若要完成自己認定的打算,確實困難。

羅馬城不是一天建成的,我所使用的工具,更不是一到手就擁有一切。欠缺的功能,我慢慢地建,可用的資源,我慢慢地找、慢慢地試。整個系統沒有已經發展完成的一天,也沒有絕對完美的結果,並不是只有 Lina64 才是這樣,過去,我曾經用過的所有 FORTH 系統都是這樣。這是 FORTH 的本質,它恆在發展,沒有止境。

這次,我想完成以繪圖結果測試函數的程式。而且,我希望能夠辦到只執行一個 main 指令,系統就能完全自動達到目的。需要人工調整的工作,充其量,於測試不同函數時,可以只在程式中,重設測試上下限與函數名稱。 

這樣的要求,繪圖函數必須可由 FORTH 系統自己控制。也許需要透過一個數據檔案來傳遞大量函數值,因此就需要一套由 FORTH 自己設計出來的檔案操作程式。除此之外,FORTH 系統也要能夠將一序列的函數值自動存進檔案。 

這不是一般淺顯的程式設計問題,設計時,有一序列的規矩要遵循,能完成工作便是萬幸。

我從網上剪貼了單張常見的函數曲線圖,來源網頁已不可考,這張函數圖單純的只是用來供作自己體會函數圖形的樣式而已。希望在完成這份貼文時,能有我自己的發展成果。換句話說,程式我還沒有寫,但我心中早有腹案,我有自信能夠完成。





幾個測試成果:

























我自創的這套測試方式,由多個分開的檔案,分工做事,再總合於單一個檔案完成測試:

首先在數據產生程式 datagen.f 中,設定指定測試範圍與函數名稱。

無論測試範圍為多少,全部先行劃分成 1000 個點,然後計算出函數值,轉存入一個叫做 data.txt 的純文字數據檔案。

最後,將這個只有 1000 組數字的純文字數據檔案,交給繪圖程式 oneset.py 直接讀取使用,立即繪出圖形。

上述三個檔案的執行程序,可以在 Lina64 系統中,直接使用單一個叫做 main 的程式,總管所有的工作,一氣呵成,測試速度非常快,成果一目了然。

這些函數圖,都可以在程式中,先行修改、編寫出三項設定( 1. 在數據產生程式中給予指定測試範圍及 2. 測試函數名稱,3. 更換繪圖程式中的抬頭名稱。)後,只執行 fload main 載入程式,不用再作其他任何事情,一秒鐘內就完成顯示。

這樣的測試性能,除了表示 FORTH 程式語言的特質足夠優良外,更該感謝 Lina64 系統的設計者:Albert ven der Host,他建立了作業系統與 Lina64 之間的健全關係,否則,我不可能如此輕易地實現這樣的構想。

我在全程的測試過程中,曾經發現過許多問題,立刻回頭修正設計,拖了幾天,才在此貼出圖示成果。

回顧文首所述,別的測試方法,總令我覺得薄弱不堪、有所欠缺。這些顯示了設計得足夠健全的函數圖,是曾經歷過顯示不太健全後,回頭修改原始程式才完成的,這就是這種測試方法的妙處。不是所有的 FORTH 系統都具有這樣的性能,這也是為什麼目前我堅持只用 Lina64 FORTH 系統的主因。


展示完全正確的函數圖,不稀奇,也不能凸顯我所強調之測試重點的好處到底在那裡?

好處是透過這種技術,我就擁有常人不易獲得的可貴瑕疵對照圖。在這次發展這個技術的過程中,我獲得了許多這方面的寶貴資料,為數眾多,我單舉一例說明:


我在設計對數函數時,根據硬體數學運算處理器的基本工作原理,應該首先設計出以 2 為基底的對數函數 lb,它算是一個最基本的原生函數。隨後,以指數 e 為基底的對數函數 ln,及以 10 為基底的對數函數 log,才以次生函數的方式產生出來。

很不幸,我在進行測試時,顯示了下列這樣的函數圖。大部份函數值都是正確的,但出現了七個奇點,在這七個奇點的附近,函數值不正確,令我感到非常遺憾。




經過一番掙扎,不斷地驗證,耗了我八天的時間,我才再度確定原來所使用的演算法完全沒有問題。最後,只好強行在程式中插入了每次都將演算結果列印出來的審核程式,終於看到了出問題的地方,就只是最後一回的計算。於是,我確定了問題的根源,僅只是我自訂的演算極限最小值 epsilon 設定得不合理,原始的設定值是 1.0 * 10 ** -17 ,修正為 1.0 * 10 ** -16 後,所有的 lb 函數值就完全正確了。

一個 64 位元能表示的單整數是 19 位數:

-1 1 rshift .
9223372036854775807  OK
-1 1 rshift nlg .
19  OK

我憑此設計浮點數的四則運算時,必須犧牲一位數,也就是最精確值最多只能為 18 位數。有些函數於演算出函數值時,可能需要上百次的回算,系統中許多地方都要使用到的計算下限 epsilon 便不宜設定為太接近極限邊緣之負的 17 次方,必須再少一位數而為負的 16 次方。

數學運算處理器是使用 80 個位元來演算出結果,自然能比我只用 64 位元的演算結果更為精確幾位數。

我設計自己的浮點系統,其目的不在與硬體數學運算處理器的性能比高下,硬體製造廠商不會給我們詳細的演算技術資料,你只能跟著使用手冊中的說明,下命令來得到結果。

我若強行使用 128 位元來設計浮點系統,結果當然就能比硬體的好,但這不是我研發的目的,我的目的在要求自己完全能用純粹的軟體技術達到目的,我辦到了。

這樣的測試,是對自己設計之系統的最佳肯定方法,此後便能大方使用這套系統於數學計算方面的各種應用。我不想再浪費時間介紹自己的設計,我只想活用整套系統。因此,本網頁將暫停貼文,以便自己能有更多的時間研發我有興趣的問題。







2019年2月1日 星期五

春節摸彩程式展示

春節摸彩程式展示


Ching-Tang Tseng
Hamilton, New Zealand
2 February 2019







過年了,有個負責華人組織的朋友,請我設計一個亂數抽獎程式,在春節聯歡會上使用,今年的除夕是 2 月 4 日。我答應了,而且耗費了不少時間才落定程式。

本來,一個簡單到直接執行 『 100 rand . 』 就能解決的問題,何須如此大費周章地設計程式?

實踐才是檢驗真理的唯一標準,不要以為問題很簡單,就輕率地回答這個問題。身體力行做事情,才能真正體會出必須設計這個程式的道理。

在我個人現行使用的 FORTH 系統中,亂數產生程式是我自行建立的,建立的方法,以前在網文中已經談過許多次,這篇文章不擬再次討論,這一次,討論正確使用亂數的方法。

拿春節摸彩情況的實際要求,檢驗亂數產生程式的效果,很快就能發現,直接使用產生亂數的函數指令,不能解決抽出的獎號不得重複的問題。

假設參與抽獎的人數總共為 100 人,100 是個隨意假設的數字。在我自己設計的系統中,直接執行『 100 rand . 』,可以得到一個介於 0 到 99 之間隨機出現的數字。理論上,如果真夠隨機,那麼,執行 100 次這樣的指令,就能得出 100 個以混亂狀態出現的整套數字。在評鑑亂數產生程式的術語上,稱這種現象為『全週期』,也就是每個該出現的數字都出現過了。

我們在使用亂數產生函數 rand 時,必須認真看待它的性能,直接用,不會有全週期,亂用,甚至可能會遭遇到亂週期或根本沒有週期。產生這種現象的起因,在於亂數產生程式是根據系統單整數的處理範圍而設計的,基本上,它只在一個單整數的範圍內盡量的亂,然後才提供給別的場合使用。這麼一來,在 0 到 100 的範圍內以 rand 取得亂數時,所得亂數,算是次生亂數,不是原生亂數。

次生亂數的產生規則,有人採用只取原生亂數的最後幾位數字來獲得,單純地只用 mod 運算,就能達到目的。也有人採用只取原生亂數的前幾位數字來獲得,單純地使用 um* nip 運算,也能達到目的。這樣的產生規則,就造成了上述抽出獎號會重複的問題。舉取前兩位數字的例子來說,原生亂數在 64 位元的系統中為 18 位數,太多了,表示不便,我們改以只有五位數來等效說明,那麼,凡為 37xxx 的數字,如: 37125, 37998, 37452, ...等等等,總共有一千個,實際上是 18 位數,那就更多了。經過 100 rand 處理後,都會只得 37 。如此一來,原生夠亂的亂數,這樣使用,就不亂了。

我在實踐程式設計時,遭遇到這樣的問題,於是設計了解決問題的程式,其核心執行的部份,仍然使用 rand 亂數函數,這是一個活用亂數產生程式 rand 的實用範例。

最終落定的程式,使用了恰當的指令名稱,並標示指令執行前後,堆疊上數字的變化需求。因此,下列程式的執行內容,不需要解釋。總抽獎人數,就是那個可以被調整內容的 setting 變數,程式現成設定為 100 。



100 value setting

: LuckyArray
  create 1+ cells allot ( n -- )
  does>  swap cells  + ( n -- addr )
;

1000 LuckyArray table

0 value NewNumber
0 value LuckyAmount
0 value flag

: review ( -- )
  0 to flag    
  LuckyAmount 1+    1 
  do 
     NewNumber   I table @    =
     if -1 to flag   leave then 
  loop ;

: unique ( -- n )
  begin 
     setting rand 1+ to NewNumber    
     review    flag    0=
  until 
  1 +to LuckyAmount    
  NewNumber LuckyAmount table ! 
  NewNumber ;

: ListTable ( -- )
  cr 
  setting 1+ 1 
  do 
     i table @ 4 .R 
     i 10 mod 0=  
     if cr then 
  loop 
  cr ;

: reset ( -- )
  0 to NewNumber   0 to LuckyAmount   0 to flag
  setting 1+ 1 
  do 
     0 i table ! 
  loop
;

: >table ( n -- )
  1 +to LuckyAmount    LuckyAmount table !    
  ListTable 
;

: test  ( -- )
  reset 
  randomize
  ListTable cr cr 
  setting 1+ 1 
  do 
     unique drop 
  loop 
  ListTable cr cr
  reset
;

: main ( n --  ) 
  page    randomize 
  1+ 1 
  do 
     unique dup . (.) 
     400 ms 
     girlsay 
     key drop
  loop 
  ListTable
;

\ Usage:
\ 1. reset or test
\ 2. 3 main




上列程式完成測試後,我接受了新的設計要求,程式的執行畫面,必須在現場以投影機放大,顯示在螢幕上。抽出得獎的數字,應該同步以語音輸出。另外,需要配上前置簡短音樂,博取效果。

數字顯示時,同步以語音輸出,是我的系統原本就已擁有的性能,很容易完成。

顯示數字如果仍只採用慣常的顯示方式,字太小了,不夠清楚。於是,我花了點時間在網上搜索,安裝了一個簡易的大號字體顯示軟體,效果不是很好,但可以比不用時清楚。

每次抽出號碼前,都希望配上一些簡短的前置音樂,要尋找這樣的資源,比較耗時間。我在網上過濾了超過一千首以上的音樂,才勉強取得夠用的資源。後來,還試著自行擷取貝多芬第五交響曲中大約五秒鐘的震撼音樂,耗費了一些時間,完成設計。

FORTH 原本就是一種適合應用於即時控制的程式語言,文首的影音展示,告訴大家這樣的效果。但是,被控制的資源,不是 FORTH 的範疇,而且,網上擁有無數個能被這樣控制的資源,所以,它們都不在此處專門介紹。