Seeding the random number generator in Javascript

2019-01-01 02:05发布

问题:

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

回答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.



回答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.



回答3:

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



回答4:

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.



回答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.



回答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



回答7:

To write your own pseudo random generator is quite simple.

The suggestion of Dave Scotese is useful but, as pointed out by others, it is not quite uniformly distributed.

However, it is not because of the integer arguments of sin. It\'s simply because of the range of sin, which happens to be a one dimensional projection of a circle. If you would take the angle of the circle instead it would be uniform.

So instead of sin(x) use arg(exp(i * x)) / (2 * PI).

If you don\'t like the linear order, mix it a bit up with xor. The actual factor doesn\'t matter that much either.

To generate n pseudo random numbers one could use the code:

function psora(k, n) {
  var r = Math.PI * (k ^ n)
  return r - Math.floor(r)
}
n = 42; for(k = 0; k < n; k++) console.log(psora(k, n))

Please also note that you cannot use pseudo random sequences when real entropy is needed.



回答8:

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



回答9:

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();


回答10:

I have written a function that returns a seeded random number, it uses Math.sin to have a long random number and uses the seed to pick numbers from that.

Use :

seedRandom(\"k9]:2@\", 15)

it will return your seeded number the first parameter is any string value ; your seed. the second parameter is how many digits will return.

     function seedRandom(inputSeed, lengthOfNumber){

           var output = \"\";
           var seed = inputSeed.toString();
           var newSeed = 0;
           var characterArray = [\'0\',\'1\',\'2\',\'3\',\'4\',\'5\',\'6\',\'7\',\'8\',\'9\',\'a\',\'b\',\'c\',\'d\',\'e\',\'f\',\'g\',\'h\',\'i\',\'j\',\'k\',\'l\',\'m\',\'n\',\'o\',\'p\',\'q\',\'r\',\'s\',\'t\',\'u\',\'v\',\'w\',\'y\',\'x\',\'z\',\'A\',\'B\',\'C\',\'D\',\'E\',\'F\',\'G\',\'H\',\'I\',\'J\',\'K\',\'L\',\'M\',\'N\',\'O\',\'P\',\'Q\',\'U\',\'R\',\'S\',\'T\',\'U\',\'V\',\'W\',\'X\',\'Y\',\'Z\',\'!\',\'@\',\'#\',\'$\',\'%\',\'^\',\'&\',\'*\',\'(\',\')\',\' \',\'[\',\'{\',\']\',\'}\',\'|\',\';\',\':\',\"\'\",\',\',\'<\',\'.\',\'>\',\'/\',\'?\',\'`\',\'~\',\'-\',\'_\',\'=\',\'+\'];
           var longNum = \"\";
           var counter = 0;
           var accumulator = 0;

           for(var i = 0; i < seed.length; i++){
                var a = seed.length - (i+1);
                for(var x = 0; x < characterArray.length; x++){
                     var tempX = x.toString();
                     var lastDigit = tempX.charAt(tempX.length-1);
                     var xOutput = parseInt(lastDigit);
                     addToSeed(characterArray[x], xOutput, a, i); 
                }                  
           }

                function addToSeed(character, value, a, i){
                     if(seed.charAt(i) === character){newSeed = newSeed + value * Math.pow(10, a)}
                }
                newSeed = newSeed.toString();

                var copy = newSeed;
           for(var i=0; i<lengthOfNumber*9; i++){
                newSeed = newSeed + copy;
                var x = Math.sin(20982+(i)) * 10000;
                var y = Math.floor((x - Math.floor(x))*10);
                longNum = longNum + y.toString()
           }

           for(var i=0; i<lengthOfNumber; i++){
                output = output + longNum.charAt(accumulator);
                counter++;
                accumulator = accumulator + parseInt(newSeed.charAt(counter));
           }
           return(output)
      }


回答11:

A simple approach for a fixed seed:

function fixedrandom(p){
    const seed = 43758.5453123;
    return (Math.abs(Math.sin(p)) * seed)%1;
}


回答12:

For a number between 0 and 100.

Number.parseInt(Math.floor(Math.random() * 100))