A random number generator
Today I neede a random number generator that needs to be random and scalable. And whatever I had used before wasn't good enough anymore. So I took my trusty old 'Programming in Oberon' by Wirth and Reiser (Martin, not Hans) and converted the one listed on page 12. I converted it to CARDINAL and INTEGER. Here's the definition module:
DEFINITION MODULE Random; PROCEDURE nr (base : CARDINAL) : CARDINAL; PROCEDURE initSeed (seed : CARDINAL); END Random.Could have been bigger and harder to understand I guess. With 'initSeed' you can enter a new seed for the random number generator. With 'nr' you get a new random number between '0' and 'base'. Here's how we donstructed the lot:
IMPLEMENTATION MODULE Random; IMPORT InOut, SysLib; CONST a = 16807; m = 2147483647; (* 2^31 1 *) q = 127773; (* m DIV a *) r = 2836; (* m MOD a *) VAR z, t : INTEGER; PROCEDURE nr (base : CARDINAL) : CARDINAL; VAR gamma : INTEGER; result, factor : CARDINAL; BEGIN gamma := a * (z MOD q)  r * (z DIV q); factor := m DIV base; IF gamma > 0 THEN z := gamma ELSE z := gamma + m END; result := CARDINAL (z) DIV factor; RETURN result END nr; PROCEDURE initSeed (seed : CARDINAL); BEGIN z := seed END initSeed; BEGIN SysLib.time (t); z := CARDINAL (t) END Random.When the library is initialised, the system time is used to set the seed for the generator. From then on, new values are calculated on the fly.
The 'factor' is used for scaling. I first had a simpler solution:
result := CARDINAL (z) MOD base;but that produced random numbers that were relatively close together. Now, with the factor, things look much better.
Running the random number generator
Below is a short test program to check the random number generator:
MODULE tln; IMPORT InOut, Random; VAR n : CARDINAL; BEGIN FOR n := 1 TO 100 DO InOut.WriteCard (Random.nr (1000), 10); InOut.WriteLn END END tln.It produced the following list:
Col 1  Col 2  Col 3  Col 4 

232 510 975 513 736 66 991 457 216 83 191 307 626 665 90 686 253 875 246 773 933 718 755 387 995 
976 173 13 803 548 608 190 888 224 161 961 158 655 115 607 21 834 994 581 537 190 218 637 766 956 
268 712 146 819 132 104 826 98 80 230 178 101 44 548 538 921 513 861 966 478 120 843 371 620 716 
88 95 596 192 367 443 89 527 301 294 838 625 516 119 620 607 63 925 101 772 716 866 229 118 463 
Don't show the accompanying graph of random data to an environmentalist. He'll see proof of global warming in it... Still, it's the output of the random number generator...
Checking the generator
Nice the table is, it doesn't really show the quality of the random numbers generated. The line graph is excellent proof for the environmentallists, but it's not for us, engineers. The sample was just too small. So I adapted the tln program as follows:
MODULE tln; IMPORT InOut, Random; VAR n : CARDINAL; BEGIN FOR n := 1 TO 10000 DO InOut.WriteCard (Random.nr (1024), 10); InOut.WriteLn END END tln.It now generates 10000 numbers in the range between 0 and 1023 (inclusive). And I invented a simple yet effective way to see in a glance if the generator is sound or not. Seeing is believing. Period. To spoil your fun... I won't say it was good or not. But here's the source for the visual checker program:
MODULE chkrnd; IMPORT InOut; VAR field : ARRAY [0..15] OF ARRAY [0..63] OF CHAR; dot, n, m : CARDINAL; BEGIN FOR n := 0 TO 15 DO FOR m := 0 TO 63 DO field [n, m] := ' ' END END; LOOP InOut.ReadCard (dot); IF InOut.Done () = FALSE THEN EXIT END; m := dot MOD 64; n := dot DIV 64; field [n, m] := 'X' END; InOut.WriteLn; FOR n := 0 TO 15 DO InOut.WriteString (field [n]); InOut.WriteLn END; InOut.WriteBf END chkrnd.
Sample size  Distribution 

100 

1000 

5000 

10000 
This doesn't mean the generator is true. For that, we need to make an average and standard deviation calculation (which is left for tomorrow). Still, it shows that (given a fair sample size) all values are used at least one.
Command line statistix
Linux does have a wc but nothing similar that handles statistics. But now it has: Stx. Stx takes its arguments from StdIn and pits out averga and standard deviation on StdOut.
MODULE Stx; IMPORT InOut, MathLib, NumConv, Strings; VAR sx, sx2, n : REAL; num : Strings.String; val : LONGCARD; ok : BOOLEAN; PROCEDURE Sum (val : INTEGER); VAR Val : REAL; BEGIN Val := MathLib.real (val); sx := sx + Val; sx2 := sx2 + Val * Val END Sum; PROCEDURE Avg; VAR avg : REAL; BEGIN avg := sx / n; InOut.WriteString ("Average = "); InOut.WriteReal (avg, 8, 2); InOut.WriteLn END Avg; PROCEDURE Std; VAR sigma : REAL; BEGIN sigma := MathLib.sqrt ((sx2  (sx * sx) / n) / (n  1.0) ); InOut.WriteString ("Sigma = "); InOut.WriteReal (sigma, 8, 2); InOut.WriteLn; END Std; BEGIN n := 0.0; sx := 0.0; sx2 := 0.0; LOOP InOut.ReadString (num); IF NOT InOut.Done () THEN EXIT END; NumConv.Str2Num (val, 10, num, ok); IF NOT ok THEN InOut.WriteString ("Not a number! Check "); InOut.WriteString (num); InOut.WriteLn; HALT END; Sum (INTEGER (val)); n := n + 1.0 END; InOut.WriteString ("Number of samples : "); InOut.WriteReal (n, 8, 0); InOut.WriteLn; Avg; Std; InOut.WriteString ("Done. Have a nice day."); InOut.WriteLn; InOut.WriteBf END Stx.The first version was written using CARDINALs and INTEGERs where possible, but from sample sizes exceeding 5000 you ran into problems (one of the squares overflowed into a negative number...) so I REALed the full source.
jan@Beryllium:~/modula/lib$ tln >File jan@Beryllium:~/modula/lib$ chkrnd <File XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX jan@Beryllium:~/modula/lib$ Stx <File Number of samples : 10000 Average = 510.23 Sigma = 296.64 Done. Have a nice day. jan@Beryllium:~/modula/lib$Well, that doesn't look too bad, all in all. All 10.000 values are used. The average is 510 (with a maximum of 1023, this is spot on the middle of the population. The standard deviation (for n1) is 300, which looks a bit high to me, but it shows the amount of chaos and chaos is what we wanted in the first place. Also, this is by no means a gaussian curve, so the standard deviation as such is rather meaningless. What we would need here, is a frequency chart. See how far we get here....
Frequency diagram
So it was time to make a frequency lister, similar to the chkrnd lister:
MODULE chkFrq; IMPORT InOut; VAR field : ARRAY [0..15] OF ARRAY [0..63] OF CHAR; dot, n, m : CARDINAL; BEGIN FOR n := 0 TO 15 DO FOR m := 0 TO 63 DO field [n, m] := 'A' END END; LOOP InOut.ReadCard (dot); IF InOut.Done () = FALSE THEN EXIT END; m := dot MOD 64; n := dot DIV 64; IF field [n, m] < 'Z' THEN INC (field [n, m]) END END; InOut.WriteLn; FOR n := 0 TO 15 DO InOut.WriteString (field [n]); InOut.WriteLn END; InOut.WriteBf END chkFrq.The array is now filled with A's and if a value is found, the letter is incremented. After 10000 samples, this is what it looks like:
jan@Beryllium:~/modula/lib$ chkFrq <File NLOJKKJKJMIILJMLFLKHLKHTHIHDJJMINLILIOHGHJHMJFMMDLGQPJHFJLKKJLKO PFJKLOLFLLGPKJMNHFLHOMJJMHKKOIGIJKLMOLLIMSHJGRIHJOGJKGGNFJIOUIKP EIJNLLMJDLIKMLOLFHPGNHIQHKQNLKJKMQFONKHHLHLHFIVKNQJMNKLILFPOMJHF FTOHONMJFKIPFOJLINTQMKPQEIPQGIQIKJIRJKHQJLNHGENQGIJKKJMKIJKGJJLL KIMKIFIGKIKKJQNFLGIIFRHMOKJEFIMNHLDDOPVMEGFKOMMLHIJNMEGFKKDHKFLF KFFINPHKJNIHMJJOHGNIKIILHLIHMHNMJGIJIHGIHJFKNKLICINPOGOMJOMOGLML KNLHJJFHICKGKIOLIEKLLQJIFMLMJMKKLHKIOMIITKLKKFKJJLMNHMKJLLGIFOIG MPLGFMFJGIJNNIINKJOOMHGJEJKONHLGHHLFKHGKIHHHNGIKLKKEPNMMLHHGFOMK HKPJDHMKOOGIJLMHIMIJLJKJLHIIFKPIKILHKINGLKGKQNNHJKOJJGENKEHNJMIP NLKFJOIHLJJPMKHJDJKKOKIILNMPJPHIIJNIKLLJFKJIIELMHLIMHFJLILJHHLHG JKGEPMERLQLNJHKJNJKKILFMHOKLMGKKHLLPLFGKKNKNMJKQFNJKJQGFINQKKNML JJNKKOMOKLHKHKJIOLJNJNNMMLNIJOKLSHNMJGOLPHLLHMKJJGKIMJEPREPKILLI KPKKKHNILGJMOHJLRILGNKKFOPFJJKHOHHKHHNNJHHHIMHNIMIIKMILGKMIIHGLH INJILMMGKJJRFILJGFQIHJMILIHGDJIQHMNIHGJFLPDHNNMKJJNFMNGIJQOIIIKG GPGMIHMJIEOQFFKPMHIOJCKFIILMLJFMNHIKHOLKRKJIHJLLJFILLGIOILJHIKLJ LKRINLKJNMIFNNJJKJMHLIVMNGILIONMGFJKNNGLFJTNNKHMLGJFGHOLLMMJGIHKOne C, 10 D's and the rest is more frequent than 4. The result is graphic but not easily comprehended. Perhaps I need to create a file that must be processed by Kspread. See how far we get here.
Kspread crashes when it needs to make graphs on this amount of data...
Page created 22 June 2009 and