random/src/Common/Random.m3


Copyright (C) 1994, Digital Equipment Corp.
Created September 1989 by Bill Kalsow Based on Random.mod by Mark R. Brown

MODULE Random;

IMPORT Word, Tick, TimeStamp, RandomReal;

CONST
  DefaultSeed = 1202056903;
  (* Note: good seeds must be in [LAST(INTEGER)/10 .. 9*LAST(INTEGER)/10]. *)

REVEAL
  Default = T OBJECT METHODS
    init(fixed := FALSE): Default
  END BRANDED "RandomKnuth3_2_2A.T" OBJECT
    i: [0..56];
    a: ARRAY [1..55] OF INTEGER;
  OVERRIDES
    init     := Init;
    integer  := Integer;
    real     := Real;
    longreal := Longreal;
    extended := Extended;
    boolean  := Boolean;
  END;

PROCEDURE Init (t: Default;  fixed: BOOLEAN): Default =
  VAR seed := DefaultSeed;
  BEGIN
    IF (t = NIL) THEN t := NEW (Default) END;
    IF NOT fixed THEN seed := RandomSeed () END;
    Start (t, seed);
    RETURN t;
  END Init;

PROCEDURE Start (t: Default;  seed: INTEGER) =
  VAR j, k: INTEGER;  i2: [1..54];
  BEGIN
    (* For an explanation of this initialization procedure see the Fortran
       program in Section 3.6 of Knuth Volume 2, second edition. *)
    t.a[55] := seed;
    j := seed;
    k := 1;
    FOR i := 1 TO 54 DO
      i2 := (21 * i) MOD 55;
      t.a[i2] := k;
      k := Word.Minus (j, k); (* ignore underflow *)
      j := t.a[i2];
    END;
    FOR i := 1 TO 20 DO Next55 (t) END;
  END Start;

PROCEDURE RandomSeed (): INTEGER =
  VAR
    seed, i: INTEGER;
    j := TimeStamp.Hash(TimeStamp.New());
  BEGIN
    (* XOR everything we can get our hands on. The idea here is to move
       around volitile portions of these quantities so that we will get as
       many random bits as possible.  -- Garret *)

    i := Tick.Now();
    seed := DefaultSeed;
    seed := Word.Xor (seed, Word.Rotate(j, i MOD BITSIZE(INTEGER)));
    seed := Word.Xor (seed, i);

    (* normalize the seed to .1*LAST(CARD) <= seed < .9 LAST(CARD) *)
    IF seed < 0 THEN seed :=  -(seed + 1);  END;
    IF seed > 9 * (LAST (CARDINAL) DIV 10) THEN
      seed := (seed DIV (j MOD 7 + 2)) + (i MOD 23);
    END;
    WHILE seed < LAST (CARDINAL) DIV 10 DO
      seed := seed * (j MOD 7 + 2) + (i MOD 23);
    END;

    RETURN seed;
  END RandomSeed;

PROCEDURE Next55 (t: Default) =
  BEGIN
    FOR j := 55 TO 32 BY  -1 DO
      (* DEC (t.a[j], t.a[j - 31]); (* ignore underflow *) *)
      t.a[j] := Word.Minus (t.a[j], t.a[j - 31]);
    END;
    FOR j := 31 TO 1 BY  -1 DO
      (* DEC (t.a[j], t.a[j + 24]); (* ignore underflow *) *)
      t.a[j] := Word.Minus (t.a[j], t.a[j + 24]);
    END;
    t.i := 56;
  END Next55;

CONST
  halfWordSize = Word.Size DIV 2;
  halfWordRadix = Word.LeftShift(1, halfWordSize);
  halfWordMask = halfWordRadix - 1;

PROCEDURE Integer (t: Default;  min, max: INTEGER): INTEGER =
  VAR x, rem, range: INTEGER;
  BEGIN
    <*ASSERT min <= max *>
    LOOP
      LOOP
        DEC (t.i);
        IF t.i > 0 THEN EXIT END;
        Next55 (t);
      END;
      x := t.a[t.i];

      IF (0 < min) OR (min + LAST (INTEGER) > max) THEN
        (* the range less than LAST (INTEGER) *)
        range := max - min + 1;
        IF range < halfWordRadix THEN
          VAR xl := Word.And(x, halfWordMask);
              xh := Word.RightShift(x, halfWordSize);
              res: INTEGER;
          BEGIN
            res := xl * range;
            res := Word.RightShift(res, halfWordSize);
            res := res + xh * range;
            res := Word.RightShift(res, halfWordSize);
            RETURN min + res;
          END (* BEGIN *)
        ELSE
	  x := Word.And (x, LAST (INTEGER));  (* 0 <= x <= LAST (INTEGER *)
	  rem := x MOD range;                 (* 0 <= rem < range *)
	  IF x - rem <= LAST (INTEGER) - range THEN
	    (* got a full block:  x - rem + range <= LAST (INTEGER) *)
	    RETURN min + rem;
	  END
        END (* IF *)
      ELSE
        (* the range is very large, but not complete *)
        (* so, just keep trying until we find a legal value *)
        IF (min <= x) AND (x <= max) THEN RETURN x END;
      END;
    END;
  END Integer;

PROCEDURE Boolean (t: Default): BOOLEAN =
  BEGIN
    LOOP
      DEC (t.i);
      IF t.i > 0 THEN EXIT END;
      Next55 (t);
    END;
    RETURN VAL (Word.And (1, t.a[t.i]), BOOLEAN);
  END Boolean;

PROCEDURE Real (t: Default;  min, max: REAL): REAL =
  BEGIN
    <*ASSERT min <= max *>
    RETURN (max - min) * RandomReal.Real (t) + min;
  END Real;

PROCEDURE Longreal (t: Default;  min, max: LONGREAL): LONGREAL =
  BEGIN
    <*ASSERT min <= max *>
    RETURN (max - min) * RandomReal.Longreal (t) + min;
  END Longreal;

PROCEDURE Extended (t: Default;  min, max: EXTENDED): EXTENDED =
  BEGIN
    <*ASSERT min <= max *>
    RETURN (max - min) * RandomReal.Extended (t) + min;
  END Extended;

BEGIN
  <*ASSERT Word.Size MOD 2 = 0 *>
END Random.
************************************************************ PROCEDURE Subrange (first, last: CARDINAL; t: Default): CARDINAL = VAR card, card1, range: CARDINAL; BEGIN LOOP LOOP DEC (t.i); IF t.i > 0 THEN EXIT END; Next55 (t); END; card := Word.And (2147483647, t.a[t.i]); IF first > last THEN RETURN (card); END; range := last - first; IF range = LAST (CARDINAL) THEN RETURN (card); END; INC (range); card1 := card - card MOD range; IF card1 <= LAST (CARDINAL) - range THEN RETURN (first + (card - card1)); END; END; END Subrange; ******************************************************************