Is it possible to seed the random number generator (Math.random) in Javascript?
I've implemented a number of good, short and fast Pseudorandom number generator (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 (for sake of simplicity), but accept one or more 32-bit values as the initial state of the PRNG. Similar seeds (e.g. a simple 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 of xmur3
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();
Alternatively, simply choose some dummy data to pad the seed with, and advance the generator a few times (12-20 iterations) to mix the initial state thoroughly. This is often seen in reference implementations of PRNGs, but it does limit the number of initial states.
var seed = 1337 ^ 0xDEADBEEF; // 32-bit seed with optional XOR value
// Pad seed with Phi, Pi and E.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
var rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
for (var i = 0; i < 15; i++) rand();
The output of these PRNG functions produce a positive 32-bit number (0 to 232-1) which is then converted to a floating-point number between 0-1 (0 inclusive, 1 exclusive) equivalent to Math.random()
, if you want random numbers of a specific range, read this article on MDN. If you only want the raw bits, simply remove the final division operation.
Another thing to note are the limitations of JS. Numbers can only represent whole integers up to 53-bit resolution. And when using bitwise operations, this is reduced to 32. This makes it difficult to implement algorithms written in C or C++, that use 64-bit numbers. Porting 64-bit code requires shims that can drastically reduce performance. So for the sake of simplicity and efficiency, I've only considered algorithms that use 32-bit math, as it is directly compatible with JS.
Now, onward to the the generators. (I maintain the full list with references here)
sfc32 is part of the PractRand random number testing suite (which it passes of course). sfc32 has a 128-bit state and is very fast in JS.
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 is a simple generator with a 32-bit state, but is extremely fast and has good quality (author states it passes all tests of gjrand testing suite and has a full 232 period, but I haven't verified).
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;
}
}
I would recommend this if you just need a simple but decent PRNG and don't need billions of random numbers (see Birthday problem).
As of May 2018, xoshiro128** is the new member of the Xorshift family, by Vigna & Blackman (professor Vigna was also responsible for the Xorshift128+ algorithm powering most Math.random
implementations under the hood). It is the fastest generator that offers a 128-bit state.
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;
}
}
The authors claim it passes randomness tests well (albeit with caveats). Other researchers have pointed out that fails some tests in TestU01 (particularly LinearComp and BinaryRank). In practice, it should not cause issues when floats are used (such as these implementations), but may cause issues if relying on the raw low bits.
This is JSF or 'smallprng' by Bob Jenkins (2007), the guy who made ISAAC and SpookyHash. It passes PractRand tests and should be quite fast, although not as fast as SFC.
function jsf32(a, b, c, d) {
return function() {
a |= 0; b |= 0; c |= 0; d |= 0;
var t = a - (b << 27 | b >>> 5) | 0;
a = b ^ (c << 17 | c >>> 15);
b = c + d | 0;
c = d + t | 0;
d = a + t | 0;
return (d >>> 0) / 4294967296;
}
}
LCG is extremely fast and simple, but the quality of its randomness is so low, that improper use can actually cause bugs in your program!
Nonetheless, it is significantly better than some answers suggesting to use Math.sin
or Math.PI
! It's a one-liner though, which is nice :).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
This implementation is called 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 is 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 will work, but I wouldn't use it unless you really need speed and don't care about randomness quality (what is random anyway?). Great for a game jam or a demo or something. LCGs suffer from seed correlations, so it is best to discard the first result of an LCG. And if you insist on using an LCG, adding an increment value may improve results, but it is probably an exercise in futility when much better options exist.
There seems to be other multipliers offering a 32-bit state (increased state-space):
var LCG=s=>()=>(s=Math.imul(741103597,s)>>>0)/2**32;
var LCG=s=>()=>(s=Math.imul(1597334677,s)>>>0)/2**32;
These LCG values are from: P. L'Ecuyer: A table of Linear Congruential Generators of different sizes and good lattice structure, April 30 1997.