Workshop on Scheme

Generating random numbers

A call to the 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.

(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)))))))))))))))
The location of the binding of 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

(make-rng (+ (* 100000 (get-internal-run-time)) (current-time)))
yields a generator with an effectively random seed.)


This document is available on the World Wide Web as

http://www.math.grin.edu/~stone/events/scheme-workshop/random.html


created July 10, 1995
last revised July 10, 1995

John David Stone (stone@math.grin.edu)