可播JavaScript随机数生成器


149

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

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


1
注意:为了使这个问题简短而集中,我将上面问题中的代码拆分为下面的Community Wiki答案
Ilmari Karonen 2014年

Answers:


123

一种选择是http://davidbau.com/seedrandom,它是可播种的基于RC4的Math.random()插入式替换,具有不错的属性。


18
从那以后,David Bau的seedrandom变得非常流行,以至于他将其保留在github上。很遗憾,ECMAScript这么早就被淘汰了,以至于这种语言并未包含在其中。说真的,没有播种!!!
在乔斯吃饭

2
@EatatJoes,这既是必需的也是可能的,这既是JS的耻辱又是荣耀。可以包含一个文件并获得对Math对象的向后兼容更改,这很酷。Brendan Eich,连续工作10天还不错。
布鲁诺·布鲁诺斯基

2
对于正在寻找该项目的npm页面的任何人:npmjs.com/package/seedrandom
Kip

27

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

我不确定您使用的是哪种RNG,但最好了解并记录下来,以便您了解其特征和局限性。

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

编辑:在此答案中考虑短种子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
模数不应该是2 ^ 31吗?我从wiki阅读了此算法。
Trantor Liu

3
请注意,这在某种意义上说是不正确的,因为它没有输出数学要求。换句话说,可以处理大量数字的语言将产生不同的结果。JS扼杀了大量数字并提高了精度(毕竟它们是浮点数)。
DDS

4
-1此LCG实现破坏了JavaScript中精确整数的限制,因为该整数this.a * this.state可能会导致大于2 ^ 53的数字。结果是有限的产量范围,并且对于某些种子可能是非常短的时期。此外,通常使用2的幂表示m结果会导致一些非常明显的模式,当您使用模运算而不是简单的截断时,没有理由不使用质数。
aaaaaaaaaaaa 2014年

22

如果您希望能够指定种子,则只需替换对getSeconds()和的调用getMinutes()。您可以传入一个int值,并将其一半以mod 60用作秒值,另一半以60取模以得到另一部分。

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

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

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

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


1
指向Mersenne Twister的JS实现的链接:math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/JAVASCRIPT/…–
orip

1
@orip您有3600个初始状态的来源吗?Mersenne Twister的种子是32位数字,因此PRNG应该具有40亿个初始状态-仅在初始种子确实是随机的情况下。
Tobias P.

2
@TobiasP。我指的是结合使用getSeconds()和getMinutes()进行播种的建议,60 * 60 == 3600可能的初始状态。我指的不是梅森·Twister。
orip 2012年

3
@orip好的,不清楚。您正在谈论Mersenne Twister,以及关于初始状态的下一句话;)
Tobias P.

2
问问题者没有提及他们对于任何类型的对密码敏感的应用程序都需要“适当的”随机数生成。尽管所有答案都是正确的,但只有第一段实际上与所提出的问题有关。也许添加建议解决方案的代码段。
V. Rubinetti 2015年


8

您列出的代码看起来像是Lehmer RNG。如果是这种情况,则2147483647它是最大的32位有符号整数,2147483647是最大的32位素数,并且48271是用于生成数字的全周期乘法器。

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


7

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

返回的随机数生成器函数采用1个名为的整数参数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。
  • 为了获得完美的分布,它会舍弃某些结果,而不受限制。
  • 它相对较慢,在我的机器上每秒运行约一亿次。

2
为什么会产生图案?for (var i = 0; i < 400; i++) { console.log("input: (" + i * 245 + ", " + i * 553 + ") | output: " + SeedRandom(i * 245, i * 553)(20)); }
蒂莫西·坎斯基

@TimothyKanski因为您使用错误。我不是专家,但是会发生这种情况,因为您是在每次迭代中初始化生成器,仅根据种子看到其第一个值,而不迭代生成器的后续编号。我相信这会发生在任何没有在指定间隔内对种子进行哈希处理的PRNG中。
bryc

5

如果您使用Typescript进行编程,那么我将改编克里斯托弗·汉克尔曼(Christoph Henkelmann)在该线程的答案中将Mersenne Twister实现作为打字稿类:

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

检查来源以了解更多方法。


0

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

3
哇,RandomNumberGeneratornextRandomNumber功能实际上可以追溯到1996年。它应该是Lehmer / LCG RNG。它使用一些聪明的数学方法对32位整数执行模算术,否则这些整数太小而无法包含一些中间值。事实是,JavaScript不实现32位整数,而是实现64位浮点数,并且由于该除法不是整数除法(如此代码一样),因此假定结果不是Lehmer生成器。它确实会产生一些看似随机的结果,但Lehmer生成器的保证并不适用。
aaaaaaaaaaaaa 2014年

1
createRandomNumber函数是后来添加的,几乎可以解决所有错误,最值得注意的是,每次调用都会实例化一个新的RNG,这意味着快速连续的调用将全部使用相同的float。在给定的代码中,几乎不可能'a''1'和配对'red'
aaaaaaaaaaaa 2014年

-2

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

首先,使用“ 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 2010年

@ posfan12,您好:问题的答案通常按“赞”的顺序列出,以使“奶油升到顶部”。但是,为了确保公平查看具有相同分数的答案,将以随机顺序显示它们。由于这最初是我的问题;-)我一定会很快检查出来。如果我(或其他人)认为该答案有帮助,我们将对其进行投票,并且如果我认为它是“正确”的答案,那么您还将看到绿色的复选标记。-欢迎使用StackOverflow!
scunliffe 2010年

2
-1此LCG实现破坏了JavaScript中精确整数的限制,因为该整数seedobj[0] * seedobja可能会导致大于2 ^ 53的数字。结果是有限的产量范围,并且对于某些种子可能是非常短的时期。
aaaaaaaaaaaa 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.