首页 文章

可种子JavaScript随机数生成器

提问于
浏览
123

JavaScript Math.random()函数返回0到1之间的随机值,根据当前时间自动播种(类似于我认为的Java) . 但是,我不能以任何方式为你设置自己的种子 .

如何创建一个随机数生成器,我可以提供自己的种子值,以便我可以生成一个可重复的(伪)随机数序列?

9 回答

  • 1

    一个选项是http://davidbau.com/seedrandom,这是一个基于RC4的可种子Math.random()替换,具有良好的属性 .

  • 4

    如果您不需要播种功能,只需使用 Math.random() 并在其周围构建辅助函数(例如 randRange(start, end) ) .

    我不确定您使用的RNG是什么,但最好知道并记录它,以便您了解它的特性和局限性 .

    就像Starkii所说,Mersenne Twister是一个很好的PRNG,但实施起来并不容易 . 如果你想自己动手尝试实现LCG - 它很容易,具有不错的随机性(不如Mersenne Twister),你可以使用一些流行的常量 .

    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));
    
  • 100

    如果您希望能够指定种子,则只需要将调用替换为 getSeconds()getMinutes() . 你可以传入一个int并使用mod 60的一半作为秒值,另一半使用modulo 60给你另一部分 .

    话虽这么说,这种方法看起来像垃圾 . 做适当的随机数生成非常困难 . 这个问题的一个明显问题是随机数种子基于秒和分钟 . 要猜测种子并重新创建随机数流,只需要尝试3600种不同的秒和分钟组合 . 这也意味着只有3600种不同的种子 . 这是可以纠正的,但我从一开始就怀疑这个RNG .

    如果您想使用更好的RNG,请尝试使用Mersenne Twister . 它是经过良好测试且相当强大的RNG,具有巨大的轨道和出色的性能 .

    编辑:我真的应该是正确的,并将其称为伪随机数生成器或PRNG .

    “任何使用算术方法产生随机数的人都处于犯罪状态 . ” --- John von Neumann

  • 8

    我使用Mersenne Twister的JavaScript端口:https://gist.github.com/300494它允许您手动设置种子 . 另外,正如其他答案中所提到的,Mersenne Twister是一个非常好的PRNG .

  • 5

    您列出的代码类似于Lehmer RNG . 如果是这种情况,则 2147483647 是最大的32位有符号整数, 2147483647 是最大的32位素数, 48271 是用于生成数字的全周期乘数 .

    如果这是真的,您可以修改 RandomNumberGenerator 以接受额外的参数 seed ,然后将 this.seed 设置为 seed ;但是你必须小心确保种子会导致随机数的良好分布(Lehmer可能会像这样奇怪) - 但是大多数种子都会很好 .

  • 18

    以下是可以提供自定义种子的PRNG . 调用 SeedRandom 将返回随机生成器函数 . SeedRandom 可以不带参数调用,以便使用当前时间为返回的随机函数设定种子,或者可以使用1或2个非负片段作为参数调用它,以便用这些整数播种它 . 由于浮点精度,只有1个值的播种只允许发生器启动到2 ^ 53个不同状态之一 .

    返回的随机生成器函数采用名为 limit 的1个整数参数,该限制必须在1到4294965886的范围内,该函数将返回0到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
    }
    

    使用示例:

    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.
    

    该生成器具有以下属性:

    • 它有大约2 ^ 64种不同的可能内部状态 .

    • 它的周期约为2 ^ 63,比任何人在JavaScript程序中实际需要的都多 .

    • 由于 mod 值为素数,因此无论选择的限制如何,输出中都没有简单的模式 . 这与一些简单的PRNG不同,后者表现出一些相当系统的模式 .

    • 它会丢弃一些结果,以便无论极限如何都能获得完美的分布 .

    • 它相对较慢,在我的机器上每秒运行大约1000万次 .

  • -2

    如果你在Typescript中编程,我改编了Mersenne Twister实现,这个实现是Christoph Henkelmann对这个线程作为打字稿类的回答:

    /**
     * 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);
        }
    }
    

    你可以使用它如下:

    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]
    

    检查源以获取更多方法 .

  • 10

    注意:此代码最初包含在上述问题中 . 为了保持简短和专注的问题,我已将其转移到社区Wiki的答案 .

    我发现这个代码已经开始了,它似乎可以正常工作以获得一个随机数,然后使用种子,但我不太确定逻辑是如何工作的(例如2345678901,48271和2147483647数字来自哪里) .

    function nextRandomNumber(){
      var hi = this.seed / this.Q;
      var lo = this.seed % this.Q;
      var test = this.A * lo - this.R * hi;
      if(test > 0){
        this.seed = test;
      } else {
        this.seed = test + this.M;
      }
      return (this.seed * this.oneOverM);
    }
    
    function RandomNumberGenerator(){
      var d = new Date();
      this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
      this.A = 48271;
      this.M = 2147483647;
      this.Q = this.M / this.A;
      this.R = this.M % this.A;
      this.oneOverM = 1.0 / this.M;
      this.next = nextRandomNumber;
      return this;
    }
    
    function createRandomNumber(Min, Max){
      var rand = new RandomNumberGenerator();
      return Math.round((Max-Min) * rand.next() + Min);
    }
    
    //Thus I can now do:
    var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
    var numbers = ['1','2','3','4','5','6','7','8','9','10'];
    var colors = ['red','orange','yellow','green','blue','indigo','violet'];
    var first = letters[createRandomNumber(0, letters.length)];
    var second = numbers[createRandomNumber(0, numbers.length)];
    var third = colors[createRandomNumber(0, colors.length)];
    
    alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");
    
    /*
      If I could pass my own seed into the createRandomNumber(min, max, seed);
      function then I could reproduce a random output later if desired.
    */
    
  • 22

    好的,这是我解决的解决方案 .

    首先,使用“newseed()”函数创建种子值 . 然后将种子值传递给“srandom()”函数 . 最后,“srandom()”函数返回0到1之间的伪随机值 .

    关键的一点是种子值存储在数组中 . 如果它只是一个整数或浮点数,那么每次调用函数时都会覆盖该值,因为整数,浮点数,字符串等的值直接存储在堆栈中,而不仅仅是指针,如数组和其他对象 . 因此,种子的 Value 可能保持持久 .

    最后,可以定义“srandom()”函数,使其成为“Math”对象的一种方法,但我会把它留给你来弄清楚 . ;)

    祝好运!

    JavaScript的:

    // Global variables used for the seeded random functions, below.
    var seedobja = 1103515245
    var seedobjc = 12345
    var seedobjm = 4294967295 //0x100000000
    
    // Creates a new seed for seeded functions such as srandom().
    function newseed(seednum)
    {
        return [seednum]
    }
    
    // Works like Math.random(), except you provide your own seed as the first argument.
    function srandom(seedobj)
    {
        seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
        return seedobj[0] / (seedobjm - 1)
    }
    
    // Store some test values in variables.
    var my_seed_value = newseed(230951)
    var my_random_value_1 = srandom(my_seed_value)
    var my_random_value_2 = srandom(my_seed_value)
    var my_random_value_3 = srandom(my_seed_value)
    
    // Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
    WScript.Echo(my_random_value_1)
    WScript.Echo(my_random_value_2)
    WScript.Echo(my_random_value_3)
    

    Lua 4(我的个人目标环境):

    -- Global variables used for the seeded random functions, below.
    seedobja = 1103515.245
    seedobjc = 12345
    seedobjm = 4294967.295 --0x100000000
    
    -- Creates a new seed for seeded functions such as srandom().
    function newseed(seednum)
        return {seednum}
    end
    
    -- Works like random(), except you provide your own seed as the first argument.
    function srandom(seedobj)
        seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
        return seedobj[1] / (seedobjm - 1)
    end
    
    -- Store some test values in variables.
    my_seed_value = newseed(230951)
    my_random_value_1 = srandom(my_seed_value)
    my_random_value_2 = srandom(my_seed_value)
    my_random_value_3 = srandom(my_seed_value)
    
    -- Print the values to console.
    print(my_random_value_1)
    print(my_random_value_2)
    print(my_random_value_3)
    

相关问题