Seeding the random number generator in Javascript

2019-01-01 01:57发布

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

12条回答
深知你不懂我心
2楼-- · 2019-01-01 02:18

A simple approach for a fixed seed:

function fixedrandom(p){
    const seed = 43758.5453123;
    return (Math.abs(Math.sin(p)) * seed)%1;
}
查看更多
只靠听说
3楼-- · 2019-01-01 02:22

Many people who need a seedable random-number generator in Javascript these days are using David Bau's seedrandom module.

查看更多
栀子花@的思念
4楼-- · 2019-01-01 02:24

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

查看更多
与君花间醉酒
5楼-- · 2019-01-01 02:26

Combining some of the previous answers, this is the seedable random function you are looking for:

Math.seed = function(s) {
    var mask = 0xffffffff;
    var m_w  = (123456789 + s) & mask;
    var m_z  = (987654321 - s) & mask;

    return function() {
      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;
    }
}

var myRandomFunction = Math.seed(1234);
var randomNumber = myRandomFunction();
查看更多
浪荡孟婆
6楼-- · 2019-01-01 02:29

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

First of all, remember to seed your PRNGs properly. Most of the generators below have no built-in seeding procedure, but accept one or more 32-bit values to initialize the state of the PRNG. Rather than seeding with just "1" or "123", which is low-entropy and can cause correlations (which is not good), it is best to initialize PRNGs with well-distributed data.

Thankfully, hash algorithms 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 a simple example based on the FNV1a-Mulvey hash:

function xfnv1a(str) {
    for(var i = 0, h = 2166136261 >>> 0; i < str.length; i++)
        h = Math.imul(h ^ str.charCodeAt(i), 16777619);
    return function() {
        h += h << 13; h ^= h >>> 7;
        h += h << 3;  h ^= h >>> 17;
        return (h += h << 5) >>> 0;
    }
}

This function utilizes Bret Mulvey's modified FNV1a 32-bit hash function. Each subsequent call to the returned function produces a new "random" hash value to be used as a seed in a PRNG.

Here's how you might use it:

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

// Or: 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.

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. It's 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 of gjrand's tests). 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's 4.3 billion states 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 did xorshift128+ and xoroshiro used in Google Chrome. It is notable as one of the few modern 32-bit PRNGs. xoroshiro64** is also promising, but only has a 64-bit state and has largely been replaced by xoshiro.

Use like so:

var rand = xoshiro128ss( seed(), seed(), seed(), seed() );
rand(); // 0.7410467516165227

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, if you rely on the low bits in an awkward way, it may cause an issue.

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) / 4294967295; // 2^32-1
    }
    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 runs pre-runs jsf() 20 times to disperse the initial state, which may be costly.

Used like so:

var rand = JSF( any_32bit_seed );
rand(); // 0.098275076597929

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.

Lehmer LCG

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.

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

It's a one-liner though--which is nice :). 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 the 31-bit state/period size. Great for a game jam or a demo or something.

Use like so:

var rand = LCG( any_31bit_seed );
rand(); // 0.45899124443531036

There seems to be other multipliers that get you the full 32-bit state. I have no clue if these are any better/worse 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.

查看更多
何处买醉
7楼-- · 2019-01-01 02:31

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.

查看更多
登录 后发表回答