src/Tools/random_word.ML
author wenzelm
Sat, 22 Dec 2007 14:10:24 +0100
changeset 25742 1051ef9d87c4
parent 25728 71e33d95ac55
child 30161 c26e515f1c29
permissions -rw-r--r--
added int/real/list operations; tuned interface; tuned;

(*  Title:      Tools/random_word.ML
    ID:         $Id$
    Author:     Makarius

Simple generator for pseudo-random numbers, using unboxed word
arithmetic only.  Unprotected concurrency introduces some true
randomness.
*)

signature RANDOM_WORD =
sig
  val max_word: word
  val step: word -> word
  val next_word: unit -> word
  val next_bool: unit -> bool
  val next_int: int -> int -> int
  val next_real: unit -> real
  val pick_any: 'a list -> 'a
  val pick_weighted: (int * 'a) list -> 'a
end;

structure RandomWord: RANDOM_WORD =
struct

(* random words: 0w0 <= result <= max_word *)

(*minimum length of unboxed words on all supported ML platforms*)
val _ = Word.wordSize >= 30
  orelse raise Fail ("Bad platform word size");

val max_word = 0wx3FFFFFFF;
val top_bit = 0wx20000000;

(*multiplier according to Borosh and Niederreiter (for modulus = 2^30),
  see http://random.mat.sbg.ac.at/~charly/server/server.html*)
val a = 0w777138309;
fun step x = Word.andb (a * x + 0w1, max_word);

local val rand = ref 0w1
in fun next_word () = (change rand step; ! rand) end;

(*NB: higher bits are more random than lower ones*)
fun next_bool () = Word.andb (next_word (), top_bit) = 0w0;


(* random integers: i <= result <= j *)

val max_int = Word.toInt max_word;

fun next_int i j =
  let val k = j - i in
    if k < 0 orelse k > max_int then raise Fail ("next_int: out of range")
    else if k < max_int then i + Word.toInt (Word.mod (next_word (), Word.fromInt (k + 1)))
    else i + Word.toInt (next_word ())
  end;


(* random reals: 0.0 <= result < 1.0 *)

val scaling = real max_int + 1.0;
fun next_real () = real (Word.toInt (next_word ())) / scaling;


(* random list elements *)

fun pick_any [] = raise Empty
  | pick_any xs = nth xs (next_int 0 (length xs - 1));

fun pick_weighted xs =
  let
    val total = fold (fn (k, _) => fn i =>
      if k < 0 then raise Fail ("Bad weight: " ^ string_of_int k) else k + i) xs 0;
    fun pick n ((k, x) :: xs) =
        if n <= k then x else pick (n - k) xs
      | pick _ [] = raise Empty;
  in if total = 0 then raise Empty else pick (next_int 1 total) xs end;

end;