-- File: RandomCardImpl.Mesa
-- Last edited by MBrown on August 27, 1982 3:18 pm

  DIRECTORY
    RandomCard USING[ErrorType],
    Inline USING[LowHalf],
    System USING[GetClockPulses];

RandomCardImpl: CEDAR MONITOR
  IMPORTS
    Inline,
    System
  EXPORTS
    RandomCard
  = {
  -- Module to produce a random sequence of cardinals.

  Error: PUBLIC ERROR [ec: RandomCard.ErrorType] = CODE;


  -- Constant definitions (meanings described in InitRandom below)

  defaultSeed: CARDINAL = 27183;
  numCalls: INTEGER = 3;


  -- Module state
  
  a: ARRAY [0..55] OF CARDINAL;
    -- Holds 55 random cardinals, to be returned by Random. (A[0] is wasted to make the
    --code generator produce better array accesses.)
  p: INTEGER [0..55];
    -- a[1..p-1] has not yet been returned by Random; p is [1..55] except within Random.


  -- Procedures

  Init: PUBLIC ENTRY PROC [seed: INTEGER] RETURNS [INTEGER] = {
    -- The parameter seed determines the sequence generated by procedure Next below.
    -- If seed=0, a default seed value is used to determine the starting point of the
    --sequence; if seed>0, seed is scaled if necessary and then used;  if seed<0, a seed
    --value is derived from the system clock.  In any case, the seed value actually used
    --(after scaling) is the integer value returned.

    minSeed: CARDINAL = LAST[CARDINAL]/10;
    g, gPrev, gSave: CARDINAL;

    PickSeed: INTERNAL PROC RETURNS [INTEGER] = TRUSTED INLINE {
      -- Returns a "random" seed in [0..LAST[INTEGER]].
      RETURN[ABS[LOOPHOLE[Inline.LowHalf[System.GetClockPulses[]],INTEGER]]];
    };--PickSeed

    IF seed<0 THEN seed ← PickSeed[];
    IF seed=0 THEN seed ← defaultSeed;
    -- Now scale the seed into the proper range (no log routine available...)
    WHILE seed<minSeed DO seed ← seed*3 ENDLOOP;
    -- Seed can't be too big since LAST[INTEGER] < LAST[CARDINAL]*(9/10)

    -- The array a is initialized by placing seed in a[55], and scattering the values

    --  (-1)**(i-1) * (F(i) - seed*F(i-1)) MOD maxRand, 0<i<55,

    -- (where F(i) denotes the i-th Fibonacci number) throughout the rest of a.  Then
    --the generating procedure RandomGen is called, numCalls times, to make things
    --sufficiently random.

    a[55] ← gPrev ← seed;
    g ← 1;
    FOR i: INTEGER IN [1..54] DO
      p ← (21*i) MOD 55;
      a[p] ← gSave ← g;  g ← gPrev-g;  gPrev ← gSave;
      ENDLOOP;
    THROUGH [1..numCalls) DO
      RandomGen[];
      ENDLOOP;
    -- Show a as being empty;  first call to Random will call RandomGen again. 
    p ← 1;
    RETURN[seed]
    };--InitRandom


  Next: PUBLIC ENTRY PROC [] RETURNS[CARDINAL] = {
    p ← p-1;
    IF p=0 THEN {  RandomGen[];  p ← 55  };
    RETURN[a[p]] };


  RandomGen: INTERNAL PROC [] = {
    -- Additive random number generator using the recurrence  y(n) = y(n-55) - y(n-24)
    --mod maxRand. See John F. Reiser, "Analysis of Additive Random Number Generators",
    --STAN-CS-77-601, March 1977, or Knuth Volume 2 (second edition.)
    FOR i: INTEGER IN [1..24] DO 
      a[i] ← a[i] - a[i+31];
      ENDLOOP;
    FOR i: INTEGER IN [25..55] DO 
      a[i] ← a[i] - a[i-24];
      ENDLOOP };


  Choose: PUBLIC--EXTERNAL--PROC[min, max: CARDINAL]
    RETURNS[CARDINAL--[min..max]--] = {
    Next: ENTRY PROC [] RETURNS[CARDINAL] = INLINE {
      -- INLINE copy of public Random above.
      p ← p-1;
      IF p=0 THEN {  RandomGen[];  p ← 55  };
      RETURN[a[p]] };
    intervalLen: CARDINAL;
    IF min > max THEN ERROR Error [badInterval];
    intervalLen ← max - min + 1; --is 0 when min=0, max=LAST[CARDINAL]
    DO
      -- Draw a number in [0..LAST[CARDINAL]].  We want to reject this number if it lies in the
      --"odd interval" at the high end of this range (there is no odd interval if intervalLen
      --divides 2↑16).  The funny test below does it (claim).  The average number of numbers
      --drawn is less than 2, and much closer to 1 for small intervalLen.
      r, rem: CARDINAL;
      r ← Next[];
      IF intervalLen = 0 THEN RETURN[r];
      rem ← r MOD intervalLen;
      IF (r - rem) > LOOPHOLE[-LOOPHOLE[intervalLen,INTEGER],CARDINAL] THEN LOOP;
      RETURN[min + rem];
      ENDLOOP };

  [] ← Init [seed: 0];

}.--RandomCardImpl

CHANGE LOG

Created by MBrown on June 26, 1979

Changed by MBrown on May 3, 1980  12:50 PM
-- Renamed module to conform to Cedar standards.  Commented-out all subrange
--declarations on local integer variables, since subranges that don't start at 0 produce
--bad code.  Fixed bug in InitRandom that caused it to loop if PickSeed returned 0. Made
--InitRandom return an INTEGER, so that runs are truly repeatable.

Changed by MBrown on August 24, 1980  10:44 PM
-- Made some minor changes to improve the code generated for this module by the Mesa
--compiler.  Array a is now 0-origin.  Array index p is now referenced more times in the
--code than a (a redundant p←1 is used), making it global 0.  PickSeed and RandomGen are
--now INLINE (each is "called" once).  The former calls to RandomGen from InitRandom are
--simulated by setting p←1 and then calling Random.
-- Results: 134 instructions (was 158), 176 bytes (was 206).  Typical path through
--Random (p#0) is 11 instructions (was 14), 13 bytes (was 19).  A clever code generator
--could still replace one LG0 with a PUSH in the typical path.
-- One observation: since INLINE expansion may decrease locality and actually slow down
--a program on a machine with a cache (Dorado), it might be useful to have control over
--whether inline expansion really happens inline, or is handled by branching to and from
--the code.  This would be unnecessary if an intelligent optimizer performed code-motion
--based on user hints (e.g. what percentage of the time does a test succeed).

Changed by MBrown on September 24, 1980  10:29 PM
-- Added Choose on suggestion of Jim Mitchell (but using my algorithm).  Undid the business of
--making RandomGen INLINE, to allow Random to be expanded inline in Choose.

Changed by MBrown on December 6, 1980  9:36 PM
-- Conversion to Pilot.  Use GetClockPulses to generate random seed.

Changed by MBrown on March 9, 1982 11:23 am
-- Converted into a module monitor.

Changed by MBrown on June 8, 1982 11:27 am
-- Changed proc names Next from Random, Init from InitRandom, made module CEDAR.

Changed by MBrown on August 27, 1982 3:19 pm
-- Call Init[0] on start trap.