Home  >  Q&A  >  body text

Seeding a random number generator in Javascript

<p>Is it possible to seed a random number generator (<code>Math.random</code>) in JavaScript? </p>
P粉006977956P粉006977956423 days ago594

reply all(2)I'll reply

  • P粉482108310

    P粉4821083102023-08-24 22:35:05

    No, it is not possible to sow Math.random(). The ECMAScript specification is intentionally vague on the subject, providing no seeding method or requiring browsers to even use the same algorithm. So such functionality has to be provided externally, which luckily is not too difficult.

    I've implemented a number of nice, short and fast pseudorandom number generator (PRNG) functions in pure JavaScript. All of these can be seeded and provide high quality numbers. These are not for security purposes - if you need a seedable CSPRNG, check out ISAAC.

    First, take care to initialize your PRNG correctly. For simplicity, the generator below does not have a built-in seed generation process, but accepts one or more 32-bit numbers as the initial seed state of the PRNG. Similar or sparse seeds (e.g. simple seeds of 1 and 2) have low entropy and can cause correlation or other randomness quality issues, sometimes resulting in outputs with similar properties (e.g. randomly generated levels are similar). To avoid this, best practice is to initialize the PRNG with a uniformly distributed high-entropy seed and/or advance beyond the first 15 or so numbers.

    There are many ways to do this, but here are two. First, Hash functions are very good at generating seeds from short strings. Even if two strings are similar, a good hash function will produce very different results, so you don't have to put too much thought into the strings. Here is an example of a hash function:

    function cyrb128(str) {
        let h1 = 1779033703, h2 = 3144134277,
            h3 = 1013904242, h4 = 2773480762;
        for (let i = 0, k; i < str.length; i++) {
            k = str.charCodeAt(i);
            h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
            h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
            h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
            h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
        }
        h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
        h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
        h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
        h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
        h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1;
        return [h1>>>0, h2>>>0, h3>>>0, h4>>>0];
    }

    Calling cyrb128 will generate a 128-bit hash value from the string that can be used to seed the PRNG. Here's how you can use it:

    // Create cyrb128 state:
    var seed = cyrb128("apples");
    // Four 32-bit component hashes provide the seed for sfc32.
    var rand = sfc32(seed[0], seed[1], seed[2], seed[3]);
    
    // Only one 32-bit component hash is needed for mulberry32.
    var rand = mulberry32(seed[0]);
    
    // Obtain sequential random numbers like so:
    rand();
    rand();

    Note: If you want a slightly more powerful 128-bit hash, consider MurmurHash3_x86_128, which is more thorough but is intended for use with large arrays.

    Alternatively, just choose some dummy data to populate the seed and advance the generator a few times (12-20 iterations) beforehand to thoroughly mix the initial state. This has the advantage of being simpler, and is often used in reference implementations of PRNG, 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();

    Note: The output of these PRNG functions generates a positive 32-bit number (0 to 232-1), which is then converted to a floating point number between 0-1 (0 included), 1) is equivalent to Math.random(), if you want a specific range of random numbers, read this article on MDN. If you just want the original bits, just remove the last division operation.

    JavaScript numbers can only represent integers up to 53-bit resolution. When bitwise operations are used, this value is reduced to 32. Modern PRNGs in other languages ​​often use 64-bit operations, which requires shims when porting to JS, which significantly reduces performance. The algorithm here only uses 32-bit operations since it is directly compatible with JS.


    sfc32 (simple fast counter)

    sfc32 is part of the PractRand random number test suite (which of course passes). sfc32 has 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;
        }
    }

    You may be wondering what | is? 0 and >>>= 0 are used. These are essentially 32-bit integer conversions used for performance optimization. Number in JS are basically floating point numbers, but they switch to 32-bit integer mode when doing bitwise operations. This mode is processed faster by the JS interpreter, but any multiplication or addition will cause it to switch back to floating point, causing a performance hit.

    mulberry32

    Mulberry32 is a simple generator with 32-bit state, but extremely fast and has good randomness (the author states that it passes the gjrand test suite with full 232 cycles, but I haven't verified that yet).

    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;
        }
    }

    If you just need a simple but decent PRNG and don't need billions of random numbers, I would recommend this (see Birthday Questions).

    xoshiro128**

    As of May 2018, xoshiro128** is a new member of the Xorshift family, developed by Vigna and Blackman (Professor Vigna is also responsible for the Math.random implementation provides support). It is the fastest generator providing 128-bit state.

    function xoshiro128ss(a, b, c, d) {
        return function() {
            var t = b << 9, r = b * 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 author claims that it passes the randomness test well (

    despite the warnings). Other researchers noted that it failed some tests in TestU01 (notably LinearComp and BinaryRank). In practice it shouldn't cause problems when working with floating point numbers (such as in these implementations), but it might if relying on the original lowest-order bit.

    JSF (Jenkins’ Little Express)

    This is the first JSF or "smallprng" by Bob Jenkins (2007).

    and Ghost Hash. It passes the PractRand test and should be reasonably fast, although not as fast as sfc32.

    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;
        }
    }

    reply
    0
  • P粉596161915

    P粉5961619152023-08-24 09:59:41

    No, it's not possible to seed Math.random(), but it's fairly easy to write your own generator, or better yet, use an existing generator.

    View: This related question.

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

    reply
    0
  • Cancelreply