Seedable JavaScript random number generator

前端 未结 9 697
暖寄归人
暖寄归人 2020-11-22 10:13

The JavaScript Math.random() function returns a random value between 0 and 1, automatically seeded based on the current time (similar to Java I believe). However, I don\'t

相关标签:
9条回答
  • 2020-11-22 10:19

    If you program in Typescript, I adapted the Mersenne Twister implementation that was brought in Christoph Henkelmann's answer to this thread as a typescript class:

    /**
     * copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
     * all rights reserved to him.
     */
    export class Random {
        static N = 624;
        static M = 397;
        static MATRIX_A = 0x9908b0df;
        /* constant vector a */
        static UPPER_MASK = 0x80000000;
        /* most significant w-r bits */
        static LOWER_MASK = 0x7fffffff;
        /* least significant r bits */
    
        mt = new Array(Random.N);
        /* the array for the state vector */
        mti = Random.N + 1;
        /* mti==N+1 means mt[N] is not initialized */
    
        constructor(seed:number = null) {
            if (seed == null) {
                seed = new Date().getTime();
            }
    
            this.init_genrand(seed);
        }
    
        private init_genrand(s:number) {
            this.mt[0] = s >>> 0;
            for (this.mti = 1; this.mti < Random.N; this.mti++) {
                var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
                this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
                    + this.mti;
                /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
                /* In the previous versions, MSBs of the seed affect   */
                /* only MSBs of the array mt[].                        */
                /* 2002/01/09 modified by Makoto Matsumoto             */
                this.mt[this.mti] >>>= 0;
                /* for >32 bit machines */
            }
        }
    
        /**
         * generates a random number on [0,0xffffffff]-interval
         * @private
         */
        private _nextInt32():number {
            var y:number;
            var mag01 = new Array(0x0, Random.MATRIX_A);
            /* mag01[x] = x * MATRIX_A  for x=0,1 */
    
            if (this.mti >= Random.N) { /* generate N words at one time */
                var kk:number;
    
                if (this.mti == Random.N + 1)   /* if init_genrand() has not been called, */
                    this.init_genrand(5489);
                /* a default initial seed is used */
    
                for (kk = 0; kk < Random.N - Random.M; kk++) {
                    y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                    this.mt[kk] = this.mt[kk + Random.M] ^ (y >>> 1) ^ mag01[y & 0x1];
                }
                for (; kk < Random.N - 1; kk++) {
                    y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
                    this.mt[kk] = this.mt[kk + (Random.M - Random.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
                }
                y = (this.mt[Random.N - 1] & Random.UPPER_MASK) | (this.mt[0] & Random.LOWER_MASK);
                this.mt[Random.N - 1] = this.mt[Random.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];
    
                this.mti = 0;
            }
    
            y = this.mt[this.mti++];
    
            /* Tempering */
            y ^= (y >>> 11);
            y ^= (y << 7) & 0x9d2c5680;
            y ^= (y << 15) & 0xefc60000;
            y ^= (y >>> 18);
    
            return y >>> 0;
        }
    
        /**
         * generates an int32 pseudo random number
         * @param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
         * @return {number}
         */
        nextInt32(range:[number, number] = null):number {
            var result = this._nextInt32();
            if (range == null) {
                return result;
            }
    
            return (result % (range[1] - range[0])) + range[0];
        }
    
        /**
         * generates a random number on [0,0x7fffffff]-interval
         */
        nextInt31():number {
            return (this._nextInt32() >>> 1);
        }
    
        /**
         * generates a random number on [0,1]-real-interval
         */
        nextNumber():number {
            return this._nextInt32() * (1.0 / 4294967295.0);
        }
    
        /**
         * generates a random number on [0,1) with 53-bit resolution
         */
        nextNumber53():number {
            var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
            return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
        }
    }
    

    you can than use it as follows:

    var random = new Random(132);
    random.nextInt32(); //return a pseudo random int32 number
    random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
    random.nextNumber(); //return a a pseudo random number in range [0,1]
    

    check the source for more methods.

    0 讨论(0)
  • 2020-11-22 10:20

    If you want to be able to specify the seed, you just need to replace the calls to getSeconds() and getMinutes(). You could pass in an int and use half of it mod 60 for the seconds value and the other half modulo 60 to give you the other part.

    That being said, this method looks like garbage. Doing proper random number generation is very hard. The obvious problem with this is that the random number seed is based on seconds and minutes. To guess the seed and recreate your stream of random numbers only requires trying 3600 different second and minute combinations. It also means that there are only 3600 different possible seeds. This is correctable, but I'd be suspicious of this RNG from the start.

    If you want to use a better RNG, try the Mersenne Twister. It is a well tested and fairly robust RNG with a huge orbit and excellent performance.

    EDIT: I really should be correct and refer to this as a Pseudo Random Number Generator or PRNG.

    "Anyone who uses arithmetic methods to produce random numbers is in a state of sin."
                                                                                                                                                              --- John von Neumann

    0 讨论(0)
  • 2020-11-22 10:24

    I use a JavaScript port of the Mersenne Twister: https://gist.github.com/300494 It allows you to set the seed manually. Also, as mentioned in other answers, the Mersenne Twister is a really good PRNG.

    0 讨论(0)
  • 2020-11-22 10:25

    The following is a PRNG that may be fed a custom seed. Calling SeedRandom will return a random generator function. SeedRandom can be called with no arguments in order to seed the returned random function with the current time, or it can be called with either 1 or 2 non-negative inters as arguments in order to seed it with those integers. Due to float point accuracy seeding with only 1 value will only allow the generator to be initiated to one of 2^53 different states.

    The returned random generator function takes 1 integer argument named limit, the limit must be in the range 1 to 4294965886, the function will return a number in the range 0 to limit-1.

    function SeedRandom(state1,state2){
        var mod1=4294967087
        var mul1=65539
        var mod2=4294965887
        var mul2=65537
        if(typeof state1!="number"){
            state1=+new Date()
        }
        if(typeof state2!="number"){
            state2=state1
        }
        state1=state1%(mod1-1)+1
        state2=state2%(mod2-1)+1
        function random(limit){
            state1=(state1*mul1)%mod1
            state2=(state2*mul2)%mod2
            if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
                return random(limit)
            }
            return (state1+state2)%limit
        }
        return random
    }
    

    Example use:

    var generator1=SeedRandom() //Seed with current time
    var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
    var generator2=SeedRandom(42) //Seed with a specific seed
    var fixedVariable=generator2(7) //First value of this generator will always be
                                    //1 because of the specific seed.
    

    This generator exhibit the following properties:

    • It has approximately 2^64 different possible inner states.
    • It has a period of approximately 2^63, plenty more than anyone will ever realistically need in a JavaScript program.
    • Due to the mod values being primes there is no simple pattern in the output, no matter the chosen limit. This is unlike some simpler PRNGs that exhibit some quite systematic patterns.
    • It discards some results in order to get a perfect distribution no matter the limit.
    • It is relatively slow, runs around 10 000 000 times per second on my machine.
    0 讨论(0)
  • 2020-11-22 10:37

    If you don't need the seeding capability just use Math.random() and build helper functions around it (eg. randRange(start, end)).

    I'm not sure what RNG you're using, but it's best to know and document it so you're aware of its characteristics and limitations.

    Like Starkii said, Mersenne Twister is a good PRNG, but it isn't easy to implement. If you want to do it yourself try implementing a LCG - it's very easy, has decent randomness qualities (not as good as Mersenne Twister), and you can use some of the popular constants.

    EDIT: consider the great options at this answer for short seedable RNG implementations, including an LCG option.

    function RNG(seed) {
      // LCG using GCC's constants
      this.m = 0x80000000; // 2**31;
      this.a = 1103515245;
      this.c = 12345;
    
      this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
    }
    RNG.prototype.nextInt = function() {
      this.state = (this.a * this.state + this.c) % this.m;
      return this.state;
    }
    RNG.prototype.nextFloat = function() {
      // returns in range [0,1]
      return this.nextInt() / (this.m - 1);
    }
    RNG.prototype.nextRange = function(start, end) {
      // returns in range [start, end): including start, excluding end
      // can't modulu nextInt because of weak randomness in lower bits
      var rangeSize = end - start;
      var randomUnder1 = this.nextInt() / this.m;
      return start + Math.floor(randomUnder1 * rangeSize);
    }
    RNG.prototype.choice = function(array) {
      return array[this.nextRange(0, array.length)];
    }
    
    var rng = new RNG(20);
    for (var i = 0; i < 10; i++)
      console.log(rng.nextRange(10, 50));
    
    var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
    for (var i = 0; i < 10; i++)
      console.log(rng.choice(digits));

    0 讨论(0)
  • 2020-11-22 10:39

    One option is http://davidbau.com/seedrandom which is a seedable RC4-based Math.random() drop-in replacement with nice properties.

    0 讨论(0)
提交回复
热议问题