Seeding the random number generator in Javascript

Seeding the random number generator in Javascript

Is it possible to seed the random number generator (Math.random) in Javascript?

Solutions/Answers:

Solution 1:

No, it is not, but it’s fairly easy to write your own generator, or better yet use an existing one. Check out: this related question.

Also, see David Bau’s blog for more information on seeding.

Solution 2:

NOTE: Despite (or rather, because of) succinctness and apparent elegance, this algorithm is by no means a high-quality one in terms of randomness. Look for e.g. those listed in this answer for better results.

(Originally adapted from a clever idea presented in a comment to another answer.)

var seed = 1;
function random() {
    var x = Math.sin(seed++) * 10000;
    return x - Math.floor(x);
}

You can set seed to be any number, just avoid zero (or any multiple of Math.PI).

The elegance of this solution, in my opinion, comes from the lack of any “magic” numbers (besides 10000, which represents about the minimum amount of digits you must throw away to avoid odd patterns – see results with values 10, 100, 1000). Brevity is also nice.

It’s a bit slower than Math.random() (by a factor of 2 or 3), but I believe it’s about as fast as any other solution written in JavaScript.

Solution 3:

I’ve implemented a number of good, short and fast copy-pastable PRNG functions in plain JavaScript. All of them can be seeded and provide good quality numbers.

First of all, take care to initialize your PRNGs properly. Most of the generators below have no built-in seed generating procedure, but accept one or more 32-bit values as the initial state of the PRNG. Similar seeds (e.g. a seed of 1 and 2) can cause correlations in weaker PRNGs, resulting in the output having similar properties (such as randomly generated levels being similar). To avoid this, it is best practice to initialize PRNGs with a well-distributed seed.

Thankfully, hash functions are very good at generating seeds for PRNGs from short strings. A good hash function will generate very different results even when two strings are similar. Here’s an example based on MurmurHash3’s mixing function:

function xmur3(str) {
    for(var i = 0, h = 1779033703 ^ str.length; i < str.length; i++)
        h = Math.imul(h ^ str.charCodeAt(i), 3432918353),
        h = h << 13 | h >>> 19;
    return function() {
        h = Math.imul(h ^ h >>> 16, 2246822507);
        h = Math.imul(h ^ h >>> 13, 3266489909);
        return (h ^= h >>> 16) >>> 0;
    }
}

Each subsequent call to the return function produces a new “random” 32-bit hash value to be used as a seed in a PRNG. Here’s how you might use it:

// Create xmur3 state:
var seed = xmur3("apples");
// Output four 32-bit hashes to provide the seed for sfc32.
var rand = sfc32(seed(), seed(), seed(), seed());

// Output one 32-bit hash to provide the seed for mulberry32.
var rand = mulberry32(seed());

// Obtain sequential random numbers like so:
rand();
rand();

This is of course functional JS, but it could be objectified.

Another thing to note is that these are all 32-bit generators, which means everything is bound by 32-bit operations, simulating 32-bit C code. This turns out to be a decent compromise, since 32-bit integers get a bit of a optimization boost in modern JS engines. JS can only do 32-bit bitwise operations, and can’t even do 64-bit math anyway. JS has a limit of 53-bit integers, but even then you need a lot of trickery to utilize them efficiently. That is why Baagøe’s supposedly super-fast 53-bit Alea generator ends up slower than these implementations.

Onward to the goods (the generators).


sfc32

This gem comes from the PractRand random number testing suite, of which it passes without issue. PractRand is purportedly even more stringent than TestU01. sfc32 has a 128-bit state and is also very fast in JS (xoshiro128** is slightly faster, but worse quality). It’s probably my PRNG of choice.

function sfc32(a, b, c, d) {
    return function() {
      a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0; 
      var t = (a + b) | 0;
      a = b ^ b >>> 9;
      b = c + (c << 3) | 0;
      c = (c << 21 | c >>> 11);
      d = d + 1 | 0;
      t = t + d | 0;
      c = c + t | 0;
      return (t >>> 0) / 4294967296;
    }
}

Mulberry32

Mulberry32 is also quite fast and has good quality (author states it passes all tests of gjrand). I would recommend this if you just need a simple but decent PRNG.

It has a state of 32-bits and a full period of 232. Ideal if you only want to seed with one 32-bit integer and don’t care about the birthday problem. There is 4.3 billion possible states in Mulberry32 compared to the 340 undecillion in sfc32/xoshiro128**.

function mulberry32(a) {
    return function() {
      var t = a += 0x6D2B79F5;
      t = Math.imul(t ^ t >>> 15, t | 1);
      t ^= t + Math.imul(t ^ t >>> 7, t | 61);
      return ((t ^ t >>> 14) >>> 0) / 4294967296;
    }
}

xoshiro128**

As of May 2018, xoshiro128** is the new member of the Xorshift family. It offers a 128-bit state, and is super fast.

function xoshiro128ss(a, b, c, d) {
    return function() {
        var t = b << 9, r = a * 5; r = (r << 7 | r >>> 25) * 9;
        c ^= a; d ^= b;
        b ^= c; a ^= d; c ^= t;
        d = d << 11 | d >>> 21;
        return (r >>> 0) / 4294967296;
    }
}

This PRNG is the latest by Blackman/Vigna who also wrote the PRNGs xorshift128+ and xoroshiro that were used in Google Chrome back in 2015. It is notable as one of the few modern PRNGs with a 32-bit version. xoroshiro64** is also a promising option, but only has a 64-bit state and has largely been replaced by xoshiro.

The authors claim it passes randomness tests well (albeit with caveats). Other researchers have pointed out that fails some tests in BigCrush (particularly LinearComp and BinaryRank). But it should not matter in practice, especially if the 32-bit value is converted to a float between 0-1 like these PRNGs are. However, it may cause an issue if you rely on the low-order bits.

JSF

This is JSF or ‘smallprng’ by Bob Jenkins (2007), the guy who made ISAAC and SpookyHash. It performs well on PractRand tests and should be quite fast. The average period length is assumed to be 2^126 but ‘hasn’t been formally determined’.

function JSF(seed) {
    function jsf() {
        var e = s[0] - (s[1]<<27 | s[1]>>>5);
         s[0] = s[1] ^ (s[2]<<17 | s[2]>>>15),
         s[1] = s[2] + s[3],
         s[2] = s[3] + e, s[3] = s[0] + e;
        return (s[3] >>> 0) / 4294967296; // 2^32
    }
    seed >>>= 0;
    var s = [0xf1ea5eed, seed, seed, seed];
    for(var i=0;i<20;i++) jsf();
    return jsf;
}

This version does not need a separate seed function. But as a result, only 32-bits can be seeded and this version pre-runs jsf() 20 times to disperse the initial state, which may be costly.

If need be, the entire 128-bit state can be initialized directly and the for loop removed. I decided to keep the original construction because the author verified the cycle length of every possible 32-bit seed in the given configuration.

LCG (aka Lehmer/Park-Miller RNG or MLCG)

This one is only here to provide a better alternative to options mentioned in other answers such as the Math.sin or Math.PI methods, which are less consistent or reliable across platforms. This LCG implementation is extremely fast but only has a 31-bit state and fails some statistical tests that previously mentioned generators pass with flying colors. It’s a one-liner though—which is nice :).

var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;

This is the minimal standard RNG as proposed by Park–Miller in 1988 & 1993 and implemented in C++11 as minstd_rand. Keep in mind that the state and period are only 31-bit (31 bits give 2 billion possible states, 32 bits give double that). This is the very type of PRNG that others are trying to replace.

It’ll work, but I wouldn’t use it unless you really need speed and don’t care about randomness quality (what is random anyway?) or if you don’t mind the 31-bit state/period size. Great for a game jam or a demo or something. Also, LCGs suffer from seed correlations, so it is best to discard the first result of an LCG.

There seems to be other multipliers that do offer a full 32-bit state. I have no clue if these are any better/worse statistically than the Park-Miller one, but here they are for completeness.

var LCG=s=>()=>((s=Math.imul(741103597,s))>>>0)/2**32;
var LCG=s=>()=>((s=Math.imul(1597334677,s))>>>0)/2**32;

These multipliers are from: P. L’Ecuyer: A table of Linear Congruential Generators of different sizes and good lattice structure, April 30 1997.

Solution 4:

No, but here’s a simple pseudorandom generator, an implementation of Multiply-with-carry I adapted from Wikipedia (has been removed since):

var m_w = 123456789;
var m_z = 987654321;
var mask = 0xffffffff;

// Takes any integer
function seed(i) {
    m_w = (123456789 + i) & mask;
    m_z = (987654321 - i) & mask;
}

// Returns number between 0 (inclusive) and 1.0 (exclusive),
// just like Math.random().
function random()
{
    m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
    m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
    var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
    result /= 4294967296;
    return result;
}

EDIT: fixed seed function by making it reset m_z

EDIT2: Serious implementation flaws have been fixed

Solution 5:

Antti Sykäri’s algorithm is nice and short. I initially made a variation that replaced Javascript’s Math.random when you call Math.seed(s), but then Jason commented that returning the function would be better:

Math.seed = function(s) {
    return function() {
        s = Math.sin(s) * 10000; return s - Math.floor(s);
    };
};

// usage:
var random1 = Math.seed(42);
var random2 = Math.seed(random1());
Math.random = Math.seed(random2());

This gives you another functionality that Javascript doesn’t have: multiple independent random generators. That is especially important if you want to have multiple repeatable simulations running at the same time.

Solution 6:

Please see Pierre L’Ecuyer’s work going back to the late 1980s and early 1990s. There are others as well. Creating a (pseudo) random number generator on your own, if you are not an expert, is pretty dangerous, because there is a high likelihood of either the results not being statistically random or in having a small period. Pierre (and others) have put together some good (pseudo) random number generators that are easy to implement. I use one of his LFSR generators.

https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf

Phil Troy