George Marsaglia's PRNG

AM (21 june 2007) I came across a relatively simple PRNG recommended by George Marsaglia, arguably an authority on the subject:

 # marsaglia.tcl --
 #     Implementation of a PRNG according to George Marsaglia
 #
 namespace eval ::PRNG {
     variable mod [expr {wide(256)*wide(256)*wide(256)*wide(256)-5}]
     variable fac [expr {wide(256)*wide(32)}]
     variable x1 [expr {wide($mod*rand())}]
     variable x2 [expr {wide($mod*rand())}]
     variable x3 [expr {wide($mod*rand())}]

     puts $mod
 }

 proc ::PRNG::marsaglia {} {
     variable mod
     variable fac
     variable x1
     variable x2
     variable x3

     set xn [expr {($fac*($x3+$x2+$x1))%$mod}]

     set x1 $x2
     set x2 $x3
     set x3 $xn

     return [expr {$xn/double($mod)}]
 }

 # main --
 #     A small test
 #
 package require Tk
 package require Plotchart

 pack [canvas .c]
 set p [::Plotchart::createXYPlot .c {0 1 0.2} {0 1 0.2}]
 set x [::PRNG::marsaglia]
 $p dataconfig series -type symbol -symbol dot
 for {set i 0} {$i < 1000} {incr i} {
     set y [::PRNG::marsaglia]
     $p plot series $x $y
     set x $y
 }

The implementation has a few disadvantages: using the builtin random number generator for seeding it means you only get 2**31 different series, but the run-length is about 2**92 for each series.

AM (22 june 2007) I am thinking about a small package/module to make Monte Carlo simulations easy to do.

Here is my first attempt at such simulations: A simple Monte Carlo simulation

And here is a first attempt at a package that could be useful:

 # random.tcl --
 #     Create procedures that return various types of pseudo-random
 #     number generators (PRNGs)
 #

 # math::random --
 #     Create the namespace
 #
 namespace eval ::math::random {
     variable count 0
     variable pi    [expr {4.0*atan(1.0)}]
 }

 # prng_Binomial --
 #     Create a PRNG with a binomial distribution
 #
 # Arguments:
 #     p         Probability that the outcome will be 1
 #
 # Result:
 #     Name of a procedure that returns a binomially distributed
 #     random number
 #
 proc ::math::random::prng_Binomial {p} {
     variable count

     incr count

     set name ::math::random::PRNG_$count
     proc $name {} [string map [list P $p] {return [expr {rand()<P? 1 : 0}]}]

     return $name
 }

 # prng_Uniform --
 #     Create a PRNG with a uniform distribution in a given range
 #
 # Arguments:
 #     min       Minimum value
 #     max       Maximum value
 #
 # Result:
 #     Name of a procedure that returns a uniformly distributed
 #     random number
 #
 proc ::math::random::prng_Uniform {min max} {
     variable count

     incr count

     set name ::math::random::PRNG_$count
     set range [expr {$max-$min}]
     proc $name {} [string map [list MIN $min RANGE $range] {return [expr {MIN+RANGE*rand()}]}]

     return $name
 }

 # prng_Exponential --
 #     Create a PRNG with an exponential distribution with given mean
 #
 # Arguments:
 #     mean      Mean value
 #
 # Result:
 #     Name of a procedure that returns a uniformly distributed
 #     random number
 #
 proc ::math::random::prng_Uniform {min max} {
     variable count

     incr count

     set name ::math::random::PRNG_$count
     proc $name {} [string map [list MEAN $mean] {return [expr {-MEAN*log(rand())}]}]

     return $name
 }

 # prng_Discrete --
 #     Create a PRNG with a uniform but discrete distribution
 #
 # Arguments:
 #     n         Outcome is an integer between 0 and n-1
 #
 # Result:
 #     Name of a procedure that returns such a random number
 #
 proc ::math::random::prng_Discrete {n} {
     variable count

     incr count

     set name ::math::random::PRNG_$count
     proc $name {} [string map [list N $n] {return [expr {int(N*rand())}]}]

     return $name
 }

 # prng_Normal --
 #     Create a PRNG with a normal distribution
 #
 # Arguments:
 #     mean      Mean of the distribution
 #     stdev     Standard deviation of the distribution
 #
 # Result:
 #     Name of a procedure that returns such a random number
 #
 # Note:
 #     Use the Box-Mueller method to generate a normal random number
 #
 proc ::math::random::prng_Normal {mean stdev} {
     variable count

     incr count

     set name ::math::random::PRNG_$count
     proc $name {} [string map [list MEAN $mean STDEV $stdev] \
     {
         variable pi
         set rad [expr {sqrt(-log(rand()))}]
         set phi [expr {2.0*$pi*rand()}]
         set r   [expr {$rad*cos($phi)}]
         return [expr {MEAN + STDEV*$r}]
     }]

     return $name
 }

 # prng_Disk --
 #     Create a PRNG with a uniform distribution of points on a disk
 #
 # Arguments:
 #     rad       Radius of the disk
 #
 # Result:
 #     Name of a procedure that returns the x- and y-coordinates of
 #     such a random point
 #
 proc ::math::random::prng_Disk {rad} {
     variable count

     incr count

     set name ::math::random::PRNG_$count
     proc $name {} [string map [list RAD $rad] \
     {
         variable pi
         set rad [expr {RAD*sqrt((rand())}]
         set phi [expr {2.0*$pi*rand()}]
         set x   [expr {$rad*cos($phi)}]
         set y   [expr {$rad*sin($phi)}]
         return [list $x $y]
     }]

     return $name
 }

 # main --
 #     Test code
 #
 set bin [::math::random::prng_Binomial 0.2]

 set ones  0
 set zeros 0
 for { set i 0} {$i < 100000} {incr i} {
     if { [$bin] } {
         incr ones
     } else {
         incr zeros
     }
 }

 puts "Binomial: $ones - $zeros"

 set discrete [::math::random::prng_Discrete 10]

 for { set i 0} {$i < 100000} {incr i} {
     set v [$discrete]

     if { [info exists count($v)] } {
         incr count($v)
     } else {
         set count($v) 1
     }
 }

 puts "Discrete:"
 parray count

 set rect [::math::random::prng_Rectangle 10 3]

 puts "Rectangle:"
 for { set i 0} {$i < 10} {incr i} {
     puts [$rect]
 }

 set normal [::math::random::prng_Normal 0 1]

 puts "Normal:"
 for { set i 0} {$i < 10} {incr i} {
     puts [$normal]
 }

The idea is that each random variable will be associated with its own command. That way there can be no mixup of distributions or parameters, as there is a command specific for that variable.