【问题标题】:Seedable JavaScript random number generator可播种的 JavaScript 随机数生成器
【发布时间】:2010-09-30 06:31:37
【问题描述】:

JavaScript Math.random() 函数返回一个介于 0 和 1 之间的随机值,根据当前时间自动播种(我相信类似于 Java)。但是,我不认为有任何方法可以为您设置自己的种子。

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

【问题讨论】:

标签: javascript random seed


【解决方案1】:

如果您不需要种子功能,只需使用 Math.random() 并围绕它构建辅助函数(例如 randRange(start, end))。

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

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

编辑:请考虑this answer 的绝佳选项,用于短种子 RNG 实施,包括 LCG 选项。

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

【讨论】:

  • 模数不应该是 2^31 吗?我从wiki阅读了这个算法。
  • 请注意,这不是“正确的”,因为它不输出数学规定的内容。换句话说,可以处理这些大量数字的语言将产生不同的结果。 JS 扼杀了大数字并降低了精度(毕竟它们是浮点数)。
  • -1 这个 LCG 实现打破了 JavaScript 中精确整数的限制,因为 this.a * this.state 可能会导致大于 2^53 的数字。结果是有限的产出范围,并且对于某些种子来说可能是非常短的时期。此外,一般情况下,对m 使用 2 的幂会导致一些非常明显的模式,当您使用模数运算而不是简单的截断时,没有理由不使用素数。
【解决方案2】:

如果您使用 Typescript 进行编程,我将 Christoph Henkelmann 对该线程的回答中引入的 Mersenne Twister 实现改编为 typescript 类:

/**
 * 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]

查看更多方法的来源。

【讨论】:

    【解决方案3】:

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

    返回的随机生成器函数接受 1 个名为 limit 的整数参数,limit 必须在 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 不同,它们表现出一些非常系统的模式。
    • 它会丢弃一些结果,以便在不受限制的情况下获得完美的分布。
    • 它比较慢,在我的机器上每秒运行大约 10 000 000 次。

    【讨论】:

    • 为什么会产生一个模式? for (var i = 0; i &lt; 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
    • @TimothyKanski 因为你用错了。我不是专家,但发生这种情况是因为您在每次迭代时都初始化生成器,只看到基于种子的第一个值,而不是迭代生成器的后续数字。我相信这将发生在任何不在指定间隔内散列种子的 PRNG 中。
    • @bryc - 我认为@TimothyKanski 正在测试不同的种子是否产生不同的随机数,并且似乎存在一种模式 - 这很奇怪。我也测试过,任何很好的东西都给出了一个模式:let count = 0; setInterval(() =&gt; { console.log(SeedRandom(count++,count++)(10)); },500); Yields repeating 3,5,7,9,1
    • @DanZen 重点是,测试方法有缺陷,并不能证明随机性的质量。并不是说SeedRandom 函数是,它可能不是。但是由于熵不足,许多好的 PRNG 将无法通过此测试。将您的测试与不同的算法一起使用,我可以让一个糟糕的函数通过而一个好的函数失败:paste2.org/AkhJfgvh。坏的只是通过,因为它使用了很大的乘数。
    • 我明白了,所以你的意思是种子中需要有一些随机性,而不是每次都增加 1 或类似的东西。没有意识到这一点。干杯。
    【解决方案4】:

    注意:此代码最初包含在上述问题中。为了使问题简短而集中,我已将其移至此社区 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.
    */
    

    【讨论】:

    • 哇,RandomNumberGeneratornextRandomNumber 函数实际上可以追溯到 1996 年。它应该是 Lehmer/LCG RNG。它使用一些聪明的数学来对 32 位整数执行模运算,否则这些整数太小而无法包含一些中间值。问题是,JavaScript 没有实现 32 位整数,而是 64 位浮点数,并且由于除法不是整数除法,因此代码假定结果不是 Lehmer 生成器。它确实会产生一些看似随机的结果,但 Lehmer 生成器的保证并不适用。
    • createRandomNumber 函数是后来添加的,它几乎做错了所有事情,最值得注意的是它每次调用时都会实例化一个新的 RNG,这意味着快速连续的调用都将使用相同的漂浮。在给定的代码中,'a' 几乎不可能与除 '1''red' 之外的任何东西配对。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      好的,这是我确定的解决方案。

      首先,您使用“newseed()”函数创建一个种子值。然后将种子值传递给“srandom()”函数。最后,“srandom()”函数返回一个介于 0 和 1 之间的伪随机值。

      关键是种子值存储在数组中。如果它只是一个整数或浮点数,则每次调用函数时都会覆盖该值,因为整数、浮点数、字符串等的值直接存储在堆栈中,而不是像数组和指针那样仅存储在堆栈中其他物体。因此,种子的价值有可能保持不变。

      最后,可以定义“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)
      

      【讨论】:

      • PS - 我对 Stack Overflow 还不是很熟悉,但为什么不按时间顺序发帖?
      • 嗨@posfan12 - 问题的答案通常按“upvotes”顺序列出,以便“奶油上升到顶部”。但是,为了确保公平查看具有相同分数的答案,它们以随机顺序显示。因为这最初是我的问题;-) 我一定会很快检查出来。如果我(或其他人)觉得这个答案有帮助,我们会支持它,如果我发现它是“正确”的答案,你也会看到这个答案添加了一个绿色复选标记。 - 欢迎来到 StackOverflow!
      • -1 这个 LCG 实现打破了 JavaScript 中精确整数的限制,因为 seedobj[0] * seedobja 可能会导致大于 2^53 的数字。结果是有限的输出范围,并且对于某些种子来说可能是很短的时间。
      【解决方案7】:

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

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

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

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

      “任何使用算术方法产生随机数的人都处于犯罪状态。”
      --- 约翰·冯·诺依曼

      【讨论】:

      • Mersenne Twister 的 JS 实现的链接:math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…
      • @orip 你有 3600 个初始状态的来源吗? Mersenne Twister 由 32 位数字播种,因此 PRNG 应该有 40 亿个初始状态 - 前提是初始种子是真正随机的。
      • @TobiasP。我指的是使用 getSeconds() 和 getMinutes() 组合播种的建议,60 * 60 == 3600 个可能的初始状态。我不是指 Mersenne Twister。
      • @orip 好的,不清楚。你在谈论 Mersenne Twister 并且在下一句中谈论初始状态;)
      • 提问者没有提到他们需要为任何类型的密码敏感应用程序生成“适当的”随机数。虽然所有答案都是正确的,但只有第一段实际上与所提出的问题相关。也许添加建议解决方案的代码 sn-p。
      【解决方案8】:

      一个选项是http://davidbau.com/seedrandom,它是基于 RC4 的可种子 Math.random() 替代品,具有很好的属性。

      【讨论】:

      • David Bau 的种子随机数已经足够流行,以至于他维护了它here on github。很遗憾,ECMAScript 已经如此落后了这么久,以至于语言中没有包含这样的东西。说真的,不要播种!!!
      • @EatatJoes,这是 JS 的耻辱和荣耀,这既是必要的,也是可能的。您可以包含一个文件并对 Math 对象进行向后兼容的更改,这非常酷。 Brendan Eich 工作 10 天还不错。
      • 对于寻找此项目的 npm 页面的任何人:npmjs.com/package/seedrandom
      【解决方案9】:

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

      如果是这样,您可以修改RandomNumberGenerator 以接收一个额外的参数seed,然后将this.seed 设置为seed;但是您必须小心确保种子会产生良好的随机数分布(Lehmer 可能会像那样奇怪)——但大多数种子都可以。

      【讨论】:

        猜你喜欢
        • 2010-10-05
        • 1970-01-01
        • 2015-03-14
        • 1970-01-01
        • 2019-07-16
        相关资源
        最近更新 更多