在Javascript中播种随机数生成器

是否有可能在JavaScript中播种随机数生成器(Math.random) ?

318566 次浏览

不,不可能为Math.random()设置种子,但是编写自己的生成器相当容易,或者更好的是使用现有的生成器。

看看:这个相关的问题

另外,请参阅David Bau的博客关于播种的更多信息

不,但这里有一个简单的伪随机生成器,Multiply-with-carry的实现,我改编自维基百科(已被删除):

var m_w = 123456789;
var m_z = 987654321;
var mask = 0xffffffff;


// Takes any integer
function seed(i) {
m_w = (123456789 + i) & mask;
m_z = (987654321 - i) & mask;
}


// Returns number between 0 (inclusive) and 1.0 (exclusive),
// just like Math.random().
function random()
{
m_z = (36969 * (m_z & 65535) + (m_z >> 16)) & mask;
m_w = (18000 * (m_w & 65535) + (m_w >> 16)) & mask;
var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
}

注意:尽管(或者说,因为)简洁和明显的优雅,这个算法在随机性方面绝不是一个高质量的算法。寻找例如在这个答案中列出的更好的结果。

(最初改编自另一个答案的评论中提出的一个聪明的想法。)

var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}

您可以将seed设置为任何数字,只要避免为零(或Math.PI的任何倍数)。

在我看来,这个解决方案的优雅之处在于没有任何“神奇”数字(除了10000,它代表了您必须丢弃的最小数字数量,以避免奇怪的模式-请参阅值10One hundred.1000的结果)。简洁也很好。

它比Math.random()稍微慢一点(2或3倍),但我相信它与任何其他用JavaScript编写的解决方案一样快。

Antti Sykäri的算法很好,很短。我最初做了一个变化,当你调用Math.seed(s)时,取代JavaScript的Math.random,但后来Jason评论说,返回函数会更好:

Math.seed = function(s) {
return function() {
s = Math.sin(s) * 10000; return s - Math.floor(s);
};
};


// usage:
var random1 = Math.seed(42);
var random2 = Math.seed(random1());
Math.random = Math.seed(random2());

这为您提供了JavaScript没有的另一个功能:多个独立的随机生成器。如果您希望同时运行多个可重复的模拟,这一点尤其重要。

结合之前的一些答案,这是你正在寻找的可种子随机函数:

Math.seed = function(s) {
var mask = 0xffffffff;
var m_w  = (123456789 + s) & mask;
var m_z  = (987654321 - s) & mask;


return function() {
m_z = (36969 * (m_z & 65535) + (m_z >>> 16)) & mask;
m_w = (18000 * (m_w & 65535) + (m_w >>> 16)) & mask;


var result = ((m_z << 16) + (m_w & 65535)) >>> 0;
result /= 4294967296;
return result;
}
}


var myRandomFunction = Math.seed(1234);
var randomNumber = myRandomFunction();

编写自己的伪随机生成器非常简单。

戴夫·斯科塞斯的建议是有用的,但正如其他人指出的那样,它并不是完全均匀分布的。

然而,这并不是因为sin的整数参数。这只是因为sin的范围,恰好是一个圆的一维投影。如果取圆的角度,它就会是均匀的。

所以用arg(exp(i * x)) / (2 * PI)代替sin(x)

如果你不喜欢线性顺序,可以把它和异或混合一下。实际因素也没有那么重要。

要生成n个伪随机数,可以使用以下代码:

function psora(k, n) {
var r = Math.PI * (k ^ n)
return r - Math.floor(r)
}
n = 42; for(k = 0; k < n; k++) console.log(psora(k, n))

还请注意,当需要真实熵时,不能使用伪随机序列。

请看Pierre L'Ecuyer在20世纪80年代末和90年代初的作品。还有其他的。如果你不是专家,自己创建一个(伪)随机数生成器是相当危险的,因为结果很可能不是统计随机的,或者有一个很小的周期。Pierre(和其他人)组合了一些很容易实现的(伪)随机数生成器。我用的是他的LFSR发电机。

https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf

不,不可能播种Math.random()ECMAScript规范在这个主题上故意含糊不清,没有提供播种的方法,也不要求浏览器使用相同的算法。因此,这样的函数必须由外部提供,谢天谢地,这并不太难。

我已经在纯JavaScript中实现了许多好的、短的、快速的伪随机数生成器 (PRNG)函数。所有这些都可以播种,并提供高质量的数字。这些不是用于安全目的的——如果您需要一个可播种的CSPRNG,请查看以撒

为了简单,下面的生成器没有内置的种子生成过程,但接受一个或多个32位数字作为PRNG的初始种子状态。相似或稀疏的种子(例如1和2的简单种子)具有低熵,并可能导致相关性或其他随机性质量问题,有时导致输出具有相似的属性(例如随机生成的水平相似)。为了避免这种情况,最好的做法是使用分布良好、高熵的种子初始化prng,并且/或超过前15个左右的数字。

有很多方法可以做到这一点,但这里有两种方法。首先,哈希函数非常擅长从短字符串中生成种子。即使两个字符串相似,一个好的哈希函数也会产生非常不同的结果,所以你不必在字符串上花太多心思。下面是一个哈希函数的例子:

function cyrb128(str) {
let h1 = 1779033703, h2 = 3144134277,
h3 = 1013904242, h4 = 2773480762;
for (let i = 0, k; i < str.length; i++) {
k = str.charCodeAt(i);
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
}
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
return [(h1^h2^h3^h4)>>>0, (h2^h1)>>>0, (h3^h1)>>>0, (h4^h1)>>>0];
}

调用cyrb128将从一个可用于PRNG种子的字符串中产生一个128位哈希值。下面是你如何使用它:

// Create cyrb128 state:
var seed = cyrb128("apples");
// Four 32-bit component hashes provide the seed for sfc32.
var rand = sfc32(seed[0], seed[1], seed[2], seed[3]);


// Only one 32-bit component hash is needed for mulberry32.
var rand = mulberry32(seed[0]);


// Obtain sequential random numbers like so:
rand();
rand();

注意:如果您想要一个更健壮的128位哈希,可以考虑MurmurHash3_x86_128,它更彻底,但适用于大型数组。

或者,简单地选择一些虚拟数据来填充种子,并预先将生成器推进几次(12-20次迭代),以彻底混合初始状态。这样做的好处是更简单,并且经常在prng的参考实现中使用,但它确实限制了初始状态的数量:

var seed = 1337 ^ 0xDEADBEEF; // 32-bit seed with optional XOR value
// Pad seed with Phi, Pi and E.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
var rand = sfc32(0x9E3779B9, 0x243F6A88, 0xB7E15162, seed);
for (var i = 0; i < 15; i++) rand();

注意:这些PRNG函数的输出产生一个32位正数(0到232-1),然后转换为一个0-1(0包含,1不包含)之间的浮点数,相当于Math.random(),如果你想要特定范围的随机数,请读取MDN上的这篇文章。如果您只想要原始位,只需删除最后的除法操作。

JavaScript数字只能表示53位分辨率的整数。而当使用位操作时,它被减少到32。其他语言中的现代prng通常使用64位操作,在移植到JS时需要shims,这会降低性能。这里的算法只使用32位操作,因为它与JS直接兼容。

现在,我们来谈谈发电机。# EYZ0


sfc32(简单快速计数器)

sfc32PractRand随机数测试套件的一部分(当然它通过了测试)。sfc32有128位的状态,在JS中非常快。

function sfc32(a, b, c, d) {
return function() {
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
var t = (a + b) | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
d = d + 1 | 0;
t = t + d | 0;
c = c + t | 0;
return (t >>> 0) / 4294967296;
}
}

你可能想知道| 0>>>= 0是干什么用的。它们本质上是32位整数强制转换,用于性能优化。Number在JS中基本上是浮点数,但在按位操作时,它们切换到32位整数模式。JS解释器可以更快地处理这种模式,但任何乘法或加法都会导致它切换回浮点数,从而导致性能下降。

Mulberry32

Mulberry32是一个32位状态的简单生成器,但速度非常快,具有良好的随机性(作者声明它通过了gjrand测试套件的所有测试,并具有完整的232周期,但我还没有验证)。

function mulberry32(a) {
return function() {
var t = a += 0x6D2B79F5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
}
}

如果你只需要一个简单的体面的 PRNG,而不需要数十亿个随机数(参见生日问题),我会推荐这个方法。

xoshiro128 * *

截至2018年5月,xoshiro128 * *Xorshift家庭的新成员,由Vigna &Blackman (Vigna教授还负责支持大多数Math.random实现的Xorshift128+算法)。它是最快的生成器,提供128位状态。

function xoshiro128ss(a, b, c, d) {
return function() {
var t = b << 9, r = a * 5; r = (r << 7 | r >>> 25) * 9;
c ^= a; d ^= b;
b ^= c; a ^= d; c ^= t;
d = d << 11 | d >>> 21;
return (r >>> 0) / 4294967296;
}
}

作者声称它很好地通过了随机性测试(尽管有一些警告)。其他研究人员指出,它在TestU01中失败了一些测试(特别是LinearComp和BinaryRank)。在实践中,当使用浮点数时(例如在这些实现中),它应该不会引起问题,但如果依赖于原始最低阶位,则可能会引起问题。

JSF (Jenkins的小而快)

这是Bob Jenkins(2007)的JSF或“smallprng”,他也制作了以撒SpookyHash。它通过实践测试,应该相当快,虽然没有sfc32快。

function jsf32(a, b, c, d) {
return function() {
a |= 0; b |= 0; c |= 0; d |= 0;
var t = a - (b << 27 | b >>> 5) | 0;
a = b ^ (c << 17 | c >>> 15);
b = c + d | 0;
c = d + t | 0;
d = a + t | 0;
return (d >>> 0) / 4294967296;
}
}

现在,许多需要Javascript中可种子随机数生成器的人都在使用David Bau的种子随机模块

对于0到100之间的数。

Number.parseInt(Math.floor(Math.random() * 100))

我写了一个函数,返回一个种子随机数,它使用数学。罪恶有一个很长的随机数,并使用种子从其中挑选数字。

使用:

seedRandom("k9]:2@", 15)

它将返回您的种子号 第一个参数是任意字符串值;你的种子。 第二个参数是返回多少位数字

     function seedRandom(inputSeed, lengthOfNumber){


var output = "";
var seed = inputSeed.toString();
var newSeed = 0;
var characterArray = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','y','x','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','U','R','S','T','U','V','W','X','Y','Z','!','@','#','$','%','^','&','*','(',')',' ','[','{',']','}','|',';',':',"'",',','<','.','>','/','?','`','~','-','_','=','+'];
var longNum = "";
var counter = 0;
var accumulator = 0;


for(var i = 0; i < seed.length; i++){
var a = seed.length - (i+1);
for(var x = 0; x < characterArray.length; x++){
var tempX = x.toString();
var lastDigit = tempX.charAt(tempX.length-1);
var xOutput = parseInt(lastDigit);
addToSeed(characterArray[x], xOutput, a, i);
}
}


function addToSeed(character, value, a, i){
if(seed.charAt(i) === character){newSeed = newSeed + value * Math.pow(10, a)}
}
newSeed = newSeed.toString();


var copy = newSeed;
for(var i=0; i<lengthOfNumber*9; i++){
newSeed = newSeed + copy;
var x = Math.sin(20982+(i)) * 10000;
var y = Math.floor((x - Math.floor(x))*10);
longNum = longNum + y.toString()
}


for(var i=0; i<lengthOfNumber; i++){
output = output + longNum.charAt(accumulator);
counter++;
accumulator = accumulator + parseInt(newSeed.charAt(counter));
}
return(output)
}

对于一个固定的种子有一个简单的方法:

function fixedrandom(p){
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
}

Math.random没有,但是< em > < / em >跑图书馆解决了这个问题。它几乎拥有你能想象到的所有分布,并支持种子随机数生成。例子:

ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)

下面是Jenkins哈希的采用版本,从在这里借来

export function createDeterministicRandom(): () => number {
let seed = 0x2F6E2B1;
return function() {
// Robert Jenkins’ 32 bit integer hash function
seed = ((seed + 0x7ED55D16) + (seed << 12))  & 0xFFFFFFFF;
seed = ((seed ^ 0xC761C23C) ^ (seed >>> 19)) & 0xFFFFFFFF;
seed = ((seed + 0x165667B1) + (seed << 5))   & 0xFFFFFFFF;
seed = ((seed + 0xD3A2646C) ^ (seed << 9))   & 0xFFFFFFFF;
seed = ((seed + 0xFD7046C5) + (seed << 3))   & 0xFFFFFFFF;
seed = ((seed ^ 0xB55A4F09) ^ (seed >>> 16)) & 0xFFFFFFFF;
return (seed & 0xFFFFFFF) / 0x10000000;
};
}

你可以这样使用它:

const deterministicRandom = createDeterministicRandom()
deterministicRandom()
// => 0.9872818551957607


deterministicRandom()
// => 0.34880331158638
PHP中有一个函数srand(seed),它为特定的种子生成固定的随机值。 但是,在JS中,没有这样的内置函数

然而,我们可以编写简单而简短的函数。

步骤1 <强> < / >强:选择种子(固定数字)。< br > # EYZ0 < br > Number应为正整数且大于1,在步骤2中进一步解释

步骤2 <强> < / >强:在种子上执行sin ()函数,它将给出该数字的罪恶值。将这个值存储在变量x中。

var x;
x = Math.sin(seed); // Will Return Fractional Value between -1 & 1 (ex. 0.4059..)

sin ()方法返回一个介于-1到1之间的分数值。
我们不需要负数,因此,在第一步中选择大于1的数字。

一步<强> 3 < / >强:返回值是-1到1之间的分数值。
所以这个值乘以10使它大于1。

x = x * 10; // 10 for Single Digit Number

< >强4步< / >强:将值与10相乘以获得额外的数字

x = x * 10; // Will Give value between 10 and 99 OR
x = x * 100; // Will Give value between 100 and 999

按要求的数字相乘。

结果将是十进制的。

< >强5步< / >强:用Math's Round (Math.round ())方法删除小数点后的值。

x = Math.round(x); // This will give Integer Value.

< >强6步< / >强:通过Math.abs方法将负值转换为正数(如果有的话)

x = Math.abs(x); // Convert Negative Values into Positive(if any)

# EYZ0 < br > < br > # EYZ1

var seed = 111; // Any Number greater than 1
var digit = 10 // 1 => single digit, 10 => 2 Digits, 100 => 3 Digits and so. (Multiple of 10)


var x; // Initialize the Value to store the result
x = Math.sin(seed); // Perform Mathematical Sin Method on Seed.
x = x * 10; // Convert that number into integer
x = x * digit; // Number of Digits to be included
x = Math.round(x); // Remove Decimals
x = Math.abs(x); // Convert Negative Number into Positive

干净和优化的函数代码

function random_seed(seed, digit = 1) {
var x = Math.abs(Math.round(Math.sin(seed++) * 10 * digit));
return x;
}
< p > # EYZ0 < br > random_seed(any_number, number_of_digits)
any_number必须大于1。
number_of_digits是可选参数,如果没有通过,将返回1 Digit
random_seed(555); // 1 Digit
random_seed(234, 1); // 1 Digit
random_seed(7895656, 1000); // 4 Digit

这里的大多数答案都会产生偏颇的结果。下面是一个基于来自github的种子随机库的测试函数:

!function(f,a,c){var s,l=256,p="random",d=c.pow(l,6),g=c.pow(2,52),y=2*g,h=l-1;function n(n,t,r){function e(){for(var n=u.g(6),t=d,r=0;n<g;)n=(n+r)*l,t*=l,r=u.g(1);for(;y<=n;)n/=2,t/=2,r>>>=1;return(n+r)/t}var o=[],i=j(function n(t,r){var e,o=[],i=typeof t;if(r&&"object"==i)for(e in t)try{o.push(n(t[e],r-1))}catch(n){}return o.length?o:"string"==i?t:t+"\0"}((t=1==t?{entropy:!0}:t||{}).entropy?[n,S(a)]:null==n?function(){try{var n;return s&&(n=s.randomBytes)?n=n(l):(n=new Uint8Array(l),(f.crypto||f.msCrypto).getRandomValues(n)),S(n)}catch(n){var t=f.navigator,r=t&&t.plugins;return[+new Date,f,r,f.screen,S(a)]}}():n,3),o),u=new m(o);return e.int32=function(){return 0|u.g(4)},e.quick=function(){return u.g(4)/4294967296},e.double=e,j(S(u.S),a),(t.pass||r||function(n,t,r,e){return e&&(e.S&&v(e,u),n.state=function(){return v(u,{})}),r?(c[p]=n,t):n})(e,i,"global"in t?t.global:this==c,t.state)}function m(n){var t,r=n.length,u=this,e=0,o=u.i=u.j=0,i=u.S=[];for(r||(n=[r++]);e<l;)i[e]=e++;for(e=0;e<l;e++)i[e]=i[o=h&o+n[e%r]+(t=i[e])],i[o]=t;(u.g=function(n){for(var t,r=0,e=u.i,o=u.j,i=u.S;n--;)t=i[e=h&e+1],r=r*l+i[h&(i[e]=i[o=h&o+t])+(i[o]=t)];return u.i=e,u.j=o,r})(l)}function v(n,t){return t.i=n.i,t.j=n.j,t.S=n.S.slice(),t}function j(n,t){for(var r,e=n+"",o=0;o<e.length;)t[h&o]=h&(r^=19*t[h&o])+e.charCodeAt(o++);return S(t)}function S(n){return String.fromCharCode.apply(0,n)}if(j(c.random(),a),"object"==typeof module&&module.exports){module.exports=n;try{s=require("crypto")}catch(n){}}else"function"==typeof define&&define.amd?define(function(){return n}):c["seed"+p]=n}("undefined"!=typeof self?self:this,[],Math);


function randIntWithSeed(seed, max=1) {
/* returns a random number between [0,max] including zero and max
seed can be either string or integer */
return Math.round(new Math.seedrandom('seed' + seed)()) * max
}

测试这段代码的真正随机性:https://es6console.com/kkjkgur2/

不可能在Math中植入种子。随机函数,但是用Javascript实现一个高质量的RNG是可能的,只需很少的代码。

Javascript数字是64位浮点精度,可以表示小于2^53的所有正整数。这给我们的算法带来了一个硬限制,但在这些限制内,您仍然可以为高质量的Lehmer / LCG随机数生成器选择参数。

function RNG(seed) {
var m = 2**35 - 31
var a = 185852
var s = seed % m
return function () {
return (s = s * a % m) / m
}
}


Math.random = RNG(Date.now())

如果你想要更高质量的随机数,代价是速度慢10倍,你可以使用BigInt进行算术,并选择m刚好适合双精度的参数。

function RNG(seed) {
var m_as_number = 2**53 - 111
var m = 2n**53n - 111n
var a = 5667072534355537n
var s = BigInt(seed) % m
return function () {
return Number(s = s * a % m) / m_as_number
}
}
参见Pierre l'Ecuyer的论文,了解上述实现中使用的参数: # EYZ0 < / p >

无论你做什么,避免使用Math.sin的所有其他答案!

不,就像他们说的,不可能播种Math.random() 但你可以安装外部包,为这做准备。我使用这些包可以安装使用这些命令

npm i random-seed

该示例来自包文档。

var seed = 'Hello World',
rand1 = require('random-seed').create(seed),
rand2 = require('random-seed').create(seed);
console.log(rand1(100), rand2(100));

点击链接获取文档https://www.npmjs.com/package/random-seed

这里有很多很好的答案,但我有一个类似的问题,即我希望Java的随机数生成器和我最终在JavaScript中使用的任何东西之间的可移植性。

我找到了java-random包

假设种子相同,这两段代码有相同的输出:

Java:

Random randomGenerator = new Random(seed);
int randomInt;
for (int i=0; i<10; i++) {
randomInt = randomGenerator.nextInt(50);
System.out.println(randomInt);
}

JavaScript:

let Random = require('java-random');
let rng = new Random(seed);
for (let i=0; i<10; i++) {
let val = rng.nextInt(50);
console.log(val);
}