可种植的 JavaScript 随机数生成器

JavaScriptMath.random()函数返回一个介于0和1之间的随机值,根据当前时间自动播种(类似于我相信的 Java)。然而,我不认为有任何方法可以为它播下你自己的种子。

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

170933 次浏览

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

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

如果你想使用一个更好的 RNG,尝试 梅森旋转算法。这是一个测试良好,相当强大的 RNG 具有巨大的轨道和出色的性能。

编辑: 我真的应该是正确的,并提到这作为一个伪随机数生成器或 PRNG。

“任何使用算术方法产生随机数的人都是有罪的。”
——约翰·冯·诺伊曼

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

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

就像 Starkii 说的,梅森旋转算法是一个很好的 PRNG,但是实现起来并不容易。如果你想自己做这件事,可以尝试实现一个 立法会——它非常简单,具有良好的随机性(不像梅森旋转算法那么好) ,而且你可以使用一些流行的常量。

编辑: 考虑在 这个答案的短期可种植 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));

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

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

其中一个选项是 http://davidbau.com/seedrandom,它是一个基于 RC4的可播放的 Math.Random ()插件替代品,具有很好的属性。

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

首先,使用“ neweed ()”函数创建一个种子值。然后将种子值传递给“ 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)

我使用了一个 JavaScript 梅森旋转算法: Https://gist.github.com/300494 它允许您手动设置种子。另外,正如在其他答案中提到的,梅森旋转算法是一个非常好的 PRNG。

注意: 这段代码最初包含在上面的问题中。为了保持问题的简短和重点,我把它移到了这个 Community 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.
*/

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

返回的随机生成器函数接受一个名为 limit的整数参数,限制必须在1到4294965886的范围内,函数将返回一个数字在0到限制 -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万次。

意外收获: 打字稿版本

如果你用 typecript 编程,我把 Christoph Henkelmann 在这个线程中提出的梅森旋转算法实现改编成了一个 typecript 类:

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

检查源代码以获得更多的方法。

下面是我喜欢使用的一个相当有效但简单的 javascript PRNG 函数:

// The seed is the base number that the function works off
// The modulo is the highest number that the function can return


function PRNG(seed, modulo) {
str = `${(2**31-1&Math.imul(48271,seed))/2**31}`
.split('')
.slice(-10)
.join('') % modulo


return str
}

我希望这就是你要找的。

谢谢,@aaaaaaaaaa (接受回答)

我真的需要一个好的非库解决方案(更容易嵌入)

所以... 我做了这个类来存储种子,并允许一个 Unity 样式的“下一步”... 但保留了初始的基于 Integer 的结果

class randS {
constructor(seed=null) {
if(seed!=null) {
this.seed = seed;
} else {
this.seed = Date.now()%4645455524863;
}
this.next = this.SeedRandom(this.seed);
this.last = 0;
}
Init(seed=this.seed) {
if (seed = this.seed) {
this.next = this.SeedRandom(this.seed);
} else {
this.seed=seed;
this.next = this.SeedRandom(this.seed);
}
}
SeedRandom(state1,state2){
var mod1=4294967087;
var mod2=4294965887;
var mul1=65539;
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){
this.last = random;
return random(limit);
}
this.last = (state1+state2)%limit;
return (state1+state2)%limit;
}
this.last = random;
return random;


}
}

然后用这些... 似乎可以很好地处理随机(但可查询)的种子值(如 Minecraft) ,甚至存储最后返回的值(如果需要的话)

var rng = new randS(9005646549);
console.log(rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20));
console.log(rng.next(20) + ' ' + rng.next(20) + ' ' + rng.last);

它应该(为每个人)输出

6 7 8 14 1 12 6
9 1 1

编辑: 如果您需要重新播种,或者正在测试值,我让 init ()工作(这在我的上下文中也是必要的)