Skip to content

Instantly share code, notes, and snippets.

@chozilla
Last active February 21, 2016 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chozilla/f56bc882f52859c0bf20 to your computer and use it in GitHub Desktop.
Save chozilla/f56bc882f52859c0bf20 to your computer and use it in GitHub Desktop.
Php MersenneTwister implementation without global state.
<?php
/**
* Class BS_MersenneTwister
* implements MT 19937
*/
class BS_MersenneTwister
{
private $seed=null; // Seed
private $r = 0; // number of reseeds
private $mti=0; // The index to use when selecting a number to randomize
private $mt=array(); // 624 element array used to get random numbers
const N = 624; /* degree of recurrence */
const M = 397; /* number of parallel sequences */
const UPPER_MASK = 0x80000000; /* most significant w-r bits */
const LOWER_MASK = 0x7fffffff; /* least significant r bits */
const MERSENNE_PRIME = 0x6c078965; /* mersenne prime number */
const MATRIX_A = 0x9908b0df; /* constant vector a */
const TEMPERING_B = 0x9d2c5680; /* tempering bitmasks */
const TEMPERING_C = 0xefc60000; /* tempering bitmasks */
const TEMPERING_S = 7; /* tempering bit shift */
const TEMPERING_T = 15; /* tempering bit shift */
const TEMPERING_U = 11; /* mt tempering bit shift */
const TEMPERING_L = 18; /* mt tempering bit shift */
const MASK14 = 0x3FFF;
const MASK21 = 0x1FFFFF;
const MASK31 = 0x7FFFFFFF;
const MASK32 = 0xFFFFFFFF;
public function __construct($seed, $index=0)
{
if($index < 0) {
throw new Exception('The generator can not be initialized with a index below 0.');
}
if($seed === null) {
throw new Exception('The generator needs a valid (int)seed to be initialized.');
}
$this->seed = $seed;
$this->mti = self::N + $index;
$this->mt = array(0 => &$seed, self::N => &$seed);
for($i = 1; $i < self::N; ++$i) {
$this->mt[$i] = (self::MERSENNE_PRIME * ($this->mt[$i - 1] ^ ($this->mt[$i - 1] >> 30)) + $i) & self::MASK32;
}
}
private function _reseed()
{
static $mag01 = array(0, self::MATRIX_A);
$mt = &$this->mt;
$mti = &$this->mti;
/* generate 0 to 226 */
for ($kk=0;$kk<self::N-self::M;$kk++) {
$y = ($mt[$kk]&self::UPPER_MASK)|($mt[$kk+1]&self::LOWER_MASK);
$mt[$kk] = $mt[$kk+self::M] ^ (($y >> 1) & self::MASK31) ^ $mag01[$y & 1];
}
/* generate 227 to 622 */
for (;$kk<self::N-1;$kk++) {
$y = ($mt[$kk]&self::UPPER_MASK)|($mt[$kk+1]&self::LOWER_MASK);
$mt[$kk] = $mt[$kk+(self::M-self::N)] ^ (($y >> 1) & self::MASK31) ^ $mag01[$y & 1];
}
/* generate 623 */
$y = ($mt[self::N-1]&self::UPPER_MASK)|($mt[0]&self::LOWER_MASK);
$mt[self::N-1] = $mt[self::M-1] ^ (($y >> 1) & self::MASK31) ^ $mag01[$y & 1];
$mti -= self::N;
$this->r++;
}
private function _clamp($min, $x, $max)
{
return max($min, min($max, $x));
}
private function _mapIntoRange($src_min, $src_max, $src, $dst_min, $dst_max)
{
if ($src_max - $src_min <= 0.0000001) return $dst_min;
$r = ($this->_clamp($src_min, $src, $src_max) - $src_min) / ($src_max - $src_min);
return $dst_min + ($dst_max - $dst_min) * $r;
}
private function _checkRange($min, $max)
{
if($min > $max) {
throw new Exception('MIN can not be bigger than MAX in a random Range');
}
}
public function int32() {
$mt = &$this->mt;
$mti = &$this->mti;
while($mti >= self::N) {
$this->_reseed();
}
$y = $mt[$mti++];
/* Tempering */
$y ^= ($y >> self::TEMPERING_U) & self::MASK21;
$y ^= ($y << self::TEMPERING_S) & self::TEMPERING_B;
$y ^= ($y << self::TEMPERING_T) & self::TEMPERING_C;
$y ^= ($y >> self::TEMPERING_L) & self::MASK14;
return $y;
}
public function int($min, $max)
{
$this->_checkRange($min, $max);
return $this->int32() % ($max - $min + 1) + $min;
}
public function float($min=0, $max=1)
{
$this->_checkRange($min, $max);
$f = $this->int32() / self::MASK32;
return $this->_mapIntoRange(0,1,$f,$min,$max);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment