[comp.misc] anybody got a *good* random algorithm?

dag@hp-lsd.HP.COM (David Geiser) (07/06/87)

The header pretty much says it.  I'm operating on a PDP-11/73, which gives
the time only down to the second, so it can't use time as a seed (actually,
I guess you can get tenths of a second, but that still doesn't help).

      .....

May vi rot in /dev/null.
----------------------------------------------------------------------

The random # stuff that follows is from a recent  edition of BYTE.  I think it
was done in Pascal in the article; I've re-done it in Modula-2.  I think it is
pretty  slick and can be  adapted  to many  different  CPUs -- that's  why I'm
posting instead of E-mailing.

I had to do some "Mickey Mouse" stuff with a 'tmp' variable to work around a
compiler defect with REAL numbers. (Sorry Mickey).

If I remembered who the author(s) were, I'd give credit.... sorry.

dag





IMPLEMENTATION MODULE utils;
VAR
x, y, z : INTEGER;
x1,y1,z1: REAL;

PROCEDURE rand ( ) : REAL;
(*

Anyone who considers  arithmetical  methods of producing  random digits is, of
course, in a state of sin.

	  --John von Neumann  (1951)


...

Many  generator use a simple  multiplicative  rule to generate the next number
based on a fixed prime  number and a  multiplier.  For  instance, if the prime
number were 30269 and the multiplier 171, you would have

    x(i+1) = (171*x(i)) MOD 30269

where 'i' is the iteration number (dag) ...


The  solution  depends upon two  techniques.  First,  consider  the problem of
using a high-level  language.  The simple generator given above can be written
as x := (171 * x) MOD 30269.  In practice this coding will not work adequately
on all  machines.  The values of x are  restricted  to the range 1 - 30268 and
therefore  will fit into a 16 bit  word.  This is not true for  171*x,  so the
expression  will  malfunction  on many  machines.  You could perform the above
calculation  "double length", but this either requires machine code or is very
expensive  in both time and  space.  If we write x in the form  177*r+s  where
0<=s<=176, then

171*x = 171*s +171*177*r 
      = 171*s + 30267*r 
      = 171*s + 30269*r - 2*r

Since 30269*r MOD 30269 = 0,
 
      (171*x) MOD 30269 = (171*s - 2*r) MOD 30269

Therefore, the statement above can be written as 
x := 171 * (x MOD 177) - 2 * (x DIV 177);

There is one problem, however.  It can return a negative number if we do not
add the following:

IF x < 0 THEN x := x + 30269;


We now have a mechanism  for making a simple  generator  for a 16 bit machine.
We choose a prime just slightly less than 32768 and then a multiplier  (around
the square root of the prime).  By using the above  coding  trick, the problem
of overflow can be eliminated  with the  equivalent of five or six  additional
instrutions.

But such a simple  generator on its own is  inadequate.  It will repeat itself
too  soon, and its  statistical  properties  are not near  enough  to those of
randomness.  To improve it, we need a second  technique  that  combines two or
more generators to make a new generator that is statistically  superior to any
of its components.  To understand this method,  consider the random numbers x1
and x2 in the range  0<x<1.  We will  call the  fractional  part of x1+x2  the
combination  of  x1+x2.  It is  clear  that if x1 and x2 are  independent  and
uniformly  distributed,  then  the  conbination  of  x1+x2  is also  uniformly
distributed  over the same range of values.  Moreover, if each of x1 and x2 is
nearly  random,  the  combination  of x1+x2  will be much more  nearly so than
either of them individually.

....

Combining the generators  works  correctly  only if they are  independent.  In
fact, they cannot be completely  independent since, if the three primes are p,
q, and r, the cycle lengths are p-1, q-1, and r-1, and these  necessarily have
2 as a common  factor.  They can, however, be made nearly  independent  enough
for practical  purposes if we make sure that they have no other common factor.
This   means   that  the   cycle   length  of  the   combined   generator   is
(p-1)(q-1)(r-1)/4 which in our case is about 6.95E12.  This means that if 1000
numbers were calculated  every second, it would not repeat itself for over 220
years.


*)


VAR 
tmp : REAL;
BEGIN 
x := 171 * (x MOD 177) - 2 * (x DIV 177);
IF x < 0 THEN x := x + 30269 END;

y := 172 * (y MOD 176) - 35 * (y DIV 176);
IF y < 0 THEN y := y + 30307 END;

z := 170 * (z MOD 178) - 63 * (z DIV 178);
IF z < 0 THEN z := z + 30323 END;

x1 := FLOAT(x);
y1 := FLOAT(y);
z1 := FLOAT(z);
tmp := x1/30269.0 + y1/30307.0 + z1/30323.0;
RETURN tmp - FLOAT(TRUNC(tmp));

END rand; 


BEGIN 
(* initialize seeds.  For production runs, different values
[between 1 and 30000] should be used each time, preferably by
some automatic method such as from date and time readings if available. *)

(* I frequently just allocate a variable and then read from it to obtain
   a ~random #.  dag *)

x := 1;
y := 10000;
z := 3000;

END utils. 





LONG LIVE vi!!



=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
       "Usual disclaimer about who's responsible for my mouth."
David Geiser
Hewlett-Packard
Logic System Division
P.O.B. 617
Colorado Springs, CO 80901-0617

UUCP:       {hplabs,cbosgd}!hp-lsd!dag
CSNET:      hp-lsd!dag@hp-labs.csnet
ARPA:       hp-lsd!dag%hp-labs@csnet-relay.arpa
INTERNET:   dag@hp-lsd.HP.COM
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=