Prior Page     Next Page     This Chapter    Prior Chapter






Computer Randomness

Any procedure that generally produces a series of numbers that pass a reasonable set of statistical tests as not non-random is called a pseudo-random number generator. This definition depends on `a reasonable set of statistical tests for non-randomness'; this is a weak standard for randomness. It also ignores the unpredictable nature of randomness. This is the definition that has guided the development of computer generated random sequences during the first three decades of computer studies. It is still popular today.

Often the next number in a mechanically generated series can be easily predicted from prior numbers, even though the series may have passed many statistical tests. But for many computer uses like simulation experiments, and for some games, this definition provides enough `unpredictability' to satisfy a user.

For many years, starting in the mid 1950s, soon after the start of computers, the most common computer pseudo-random number generator was based on this computation:


The algorithm is simple: take the last pseudo-random multiply by a constant and add another constant and this gives you the next pseudo-random number. It seems odd that this should appear random, doesn't it? But because of limited numbers in computers, which cause `wrap-around' when the numbers get too big, the result is always the lower half of the answer. The middle of numbers in a linear series where the multiplier is pretty big looks uniformly distributed, and unpredictable.

This is a pretty fast algorithm, although multiplication and division are slow machine operations. This generator is sensitive to the choice of the constants c1 and c2, and c3. Usually c3 is picked to be some power of 2, usually the word size of the machine, so that division doesn't have to be used to implement the mod function; the machine wrap-around does it for you. For many library pseudo-random number generators the value of c2 is commonly zero, but this leads to a disaster if the prior random is ever zero. Usually the seed (the first pseudo-random number) is supplied by the user; and the two constants, c1 and c2, are built into the pseudo-random number generator. Generators of this form are called linear congruential random number generators or LCGs for short.

It only takes two or three numbers from an LCG and a little calculation to be able to predict future numbers. So they don't produce good randoms in the sense of not knowing the future from the past. Although used in a game where you don't see the numbers involved, it is difficult to predict what will happen next; even though the series is highly predictable.

All LCGs eventually repeat. This is called the period of the pseudo-random number generator. Any finite procedure that generates pseudo-random numbers will eventually repeat. One can make the pseudo-random sequence arbitrarily long, but eventually the procedure will run out of energy to produce new number sequences and begin to repeat itself. Thus no finite procedure can be a mathematical random sequence generator.

LCGs were the basis for most of the simulation research in the 1960s and 1970s. But LCGs have many bad properties that may make simulation results inaccurate or even completely wrong. For example if one takes pairs of sequential numbers output from an LCG and plot them one will find they group into diagonal lines on a plane. If you take the sine of the first member of the sequential pair and the cosine of the next member and plot the results you will find all the points lie on single continuous spiral. This is not an accident, it comes from the ordered nature of the linear formula interacting with the modulus. Points falling off the line of the spiral will never occur. If this `structure' of the numbers could affect simulation results then using an LCG is probably misguided. The built-in random number generator in Pascal, C, Fortran, and most other high level languages is an LCG, so beware this problem has not gone away. With some LCGs the low-order bits of the number repeat regularly, and this can cause problems as well. Used as a floating point number this has a minimal effect, used as an integer, however, it could cause `neurotic' program behavior, that you may find hard to understand.

Using mathematically well behaved functions to produce randomness is not ideal, because the predictable behavior of these functions eventually shows through in the resulting sequence. A better approach is to use arbitrary tables as functions or use exclusive-or to combine several pseudo-random sequences into one that is difficult to predict.

Recently several pseudo-random number generators have been designed that do a much better job. These new algorithms have long periods, and the numbers they produce are difficult to predict from prior numbers in the sequence.

Here is a good random number generator that you can use that is much better than an LCG. It is easy to code and difficult to predict. The following code combines two Tausworthe Random Number Generators. It is a reformating and translation of the algorithm in "Efficient and Portable Combined Tausworthe Random Number Generators" by Shu Tezuka and Pierre L'Ecuyer in the ACM Transactions on Modeling and Computer Simulation, Vol. 1, No. 2, April 1991, pages 99-112. It consists of one initialization procedure and two functions:

  PROCEDURE tauset  ( i1, i2 : longint );
  FUNCTION  taurand ( ir: longint ): longint;
  FUNCTION  tuniform: real;

The procedure tauset sets the seed from the two long integer numbers pi1, pi2.   Taurand returns a uniformly distributed integer in the range [0,ir-1]. Tuniform returns a uniformly distributed real in the range [0.0,1.0]. These routines produce 31 bits of random; the period of the generator is about 260.




VAR
state1, state2: longint;  { State of RNG: Global Variables }

PROCEDURE tauset (i1, i2: longint)
{ i1 & i2 are the initial seeds, 0,0 if you are lazy }
BEGIN
    IF ( i1 = 0 )
    THEN state1 := 648345046
    ELSE IF ( i1 > 0 )
    THEN state1 := BAND( i1, $7fffffff );



    IF ( i2 = 0 )
    THEN state2 := 384581855
    ELSE IF ( i2 > 0 )
    THEN state2 := BAND( i2, $1fffffff );
END;

FUNCTION taurand ( ir: longint ): longint
{ ir is the integer range [0..ir-1] of taurand }

VAR
b, j, s1, s2: longint;
BEGIN

    { 1st RN: 12 bit circular jumps in a 31 bit field }

    s1 := state1;
    b  := BAND( BXOR( BSL( s1,13 ), s1 ),           $7fffffff );
    s1 := BAND( BXOR( BSL( s1,12 ), BSR( b, 19 ) ), $7fffffff );
    state1 := s1;

    { 2nd RN: 17 bit circular jumps in a 29 bit field }

    s2 := state2;
    b  := BAND( BXOR( BSL( s2, 2 ), s2),            $1fffffff );
    s2 := BAND( BXOR( BSL( s2,17 ), BSR( b, 12 ) ), $1fffffff );
    state2 := s2;

    { Combine RNs into one }

    j  := BXOR( s1, BSL(s2,2) );   { align randoms at 29 bits }

    { Adjust the range }

    IF ( ir > 0 )
    THEN taurand :=  j MOD ir;
    ELSE taurand :=  j;

END;

FUNCTION tuniform: real;

    { a uniformly random number with range 0.0 to 1,0 }

    tuniform := 4.656612873e-10 * taurand(0);

Just to remind you, and so you see the relationship between Pascal code and the Hermes machine, here is the same random number program in Hermes Assembly Language.



state1   VAR 4
state2   VAR 4

TAUSET   PROC  // ( i1, i2: longint ):  returns nothing

          ST    R0,SaveR0

// Initialize the random state from the parameters

          L     R0,0[R15]
          ADD   R0,=0=
          JNZ   L1   
          L     R0,=648345046=
L1       AND   R0,=7fffffff=
          ST    R0,state1

          L     R0,4[R15]
          ADD   R0,=0=
          JNZ   L2   
          L     R0,=384581855=
L2       AND   R0,=7fffffff=
          ST    R0,state2

// Restore register and Return

          L     R0,SaveR0
          JSJ   8[R15]

TAURAND  PROC  // ( ir: longint ):  returns longint random in R1

//    ir is the integer range [0..ir-1] of taurand

          ST    R15,SaveR15
          ST    R0,SaveR0

//    1st RN: 12 bit circular jumps in a 31 bit field

          L     R0,state1
          SHFT  R0,13
          XOR   R0,state1
          AND   R0,=0x7fffffff=
          SHFT  R0,-19
          L     R1,state1
          ST    R0,state1
          SHFT  R1,12
          XOR   R1,state1
          AND   R1,=0x7fffffff=
          ST    R1,state1

//    2nd RN: 17 bit circular jumps in a 29 bit field

          L     R0,state2
          SHFT  R0,2
          XOR   R0,state2
          AND   R0,=0x1fffffff=
          SHFT  R0,-12
          L     R1,state2
          ST    R0,state2
          SHFT  R1,17
          XOR   R1,state2
          AND   R1,=0x1fffffff=
          ST    R1,state2

//    Combine the two randoms with XOR

          SHFT  R1,2
          XOR   R1,state1

//    Adjust the range

          L     R0,0[R15] // Pickup the parameter
          ADD   R0,=0=    // Set the condition code
          JNP   EXIT
          LA    R0,0      // Put zero in R0 to prepare for divide
          DIV   R0,0[R15] // Put remainder in R1

EXIT:    L     R0,SaveR0
          L     R15,SaveR15
          JSJ   4[R15]    // Jump after the parameter

          END   TAURAND

Some comments on the Hermes program for random numbers. Both TAUSET and TAURAND take parmeters these are given as 4-byte integers which follow in memory the jump to TAUSET. The return code in each case jumps over the parameter list. There is no code for Hermes TUNIFORM because there are no floating point numbers in the Hermes machine.








Prior Page     Next Page     This Chapter    Prior Chapter


Copyright © 1995 Robert Uzgalis. All Rights Reserved.
Contact: buz@cs.aukuni.ac.nz