-- 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.