make-rng
procedure presented here yields a
dynamically constructed procedure that acts as a random-number generator.
When the dynamically constructed procedure is invoked with no arguments, it
returns a pseudo-random real value evenly distributed in the range [0.0,
1.0); when it is invoked with one argument (which should be a positive
integer), it sets the seed of the generator to the specified value; when it
is invoked with two arguments alpha
and beta
(which must both be integers, with alpha
strictly less than
beta
), it returns a pseudo-random integer value evenly
distributed in the range [alpha
, beta
). The generator employs the linear-congruential method, and specifically uses a choice of multiplier that was proposed as a standard by Stephen K. Park et al. in ``Technical correspondence,'' Communications of the ACM 36 (1993), number 7, 108--110.
The location of the binding of(define make-rng (let ((multiplier 48271) (modulus 2147483647)) (define (apply-congruence current-seed) (let ((candidate (modulo (* current-seed multiplier) modulus))) (if (zero? candidate) modulus candidate))) (define (coerce proposed-seed) (if (integer? proposed-seed) (- modulus (modulo proposed-seed modulus)) 19860617)) ;; an arbitrarily chosen birthday (lambda (initial-seed) (let ((seed (coerce initial-seed))) (lambda args (case (length args) ((0) (set! seed (apply-congruence seed)) (/ (- modulus seed) modulus)) ((1) (set! seed (coerce (car args)))) ((2) (let ((alpha (car args)) (beta (cadr args))) (if (and (integer? alpha) (integer? beta) (< alpha beta)) (begin (set! seed (apply-congruence seed)) (- beta (inexact->exact (ceiling (* (- beta alpha) (/ seed modulus)))))))))))))))
seed
-- inside the body of
make-rng
, but outside the lambda-expression that denotes the
dynamically allocated procedure -- ensures that the storage location
containing the seed will be different for each invocation of
make-rng
(so that every generator that is constructed will
have an independently settable seed), yet inaccessible except through
invocations to the dynamically allocated procedure itself. In effect,
random-number generators in this implementation constitute an abstract data
type with the constructor make-rng
and exactly three
operations, corresponding to the three possible arities of a call to the
generator.
When calling this procedure, the programmer must supply an initial value
for the seed. This should be an integer (if it is not, an arbitrary
default seed is silently substituted). The value supplied is forced into
the range (0, modulus
], since it is an invariant of the
procedure that the seed must always be in this range.
To obtain an initial seed that is likely to be different each time a new generator is constructed, use some combination of the program's running time and the wall-clock time. (Most Scheme implementations provide procedures that return one or both of these quantities. For instance, in SCM, the call
yields a generator with an effectively random seed.)(make-rng (+ (* 100000 (get-internal-run-time)) (current-time)))
This document is available on the World Wide Web as
http://www.math.grin.edu/~stone/events/scheme-workshop/random.html