提问人:weepy 提问时间:2/7/2009 最后编辑:ashleedawgweepy 更新时间:9/26/2023 访问量:363596
在 Javascript 中播种随机数生成器
Seeding the random number generator in Javascript
答:
不,不可能 播种 ,但是编写自己的生成器相当容易,或者更好的是,使用现有的生成器。Math.random()
查看:这个相关问题。
另外,有关播种的更多信息,请参阅 David Bau 的博客。
不,但这里有一个简单的伪随机生成器,这是我改编自维基百科的乘进位的实现(此后已被删除):
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;
}
评论
m_w
m_z
m_w
m_z
注意:尽管(或者更确切地说,因为)简洁和明显的优雅,但就随机性而言,该算法绝不是高质量的算法。例如,寻找此答案中列出的那些以获得更好的结果。
(最初改编自评论中提出的一个聪明的想法,以回答另一个答案。
var seed = 1;
function random() {
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
您可以设置为任何数字,只需避免零(或 Math.PI 的任意倍数)。seed
在我看来,这个解决方案的优雅之处在于没有任何“神奇”的数字(除了 10000,它代表了为避免奇怪模式而必须丢弃的最小数字数量 - 请参阅值为 10、100、1000 的结果)。 简洁也很好。
它比 Math.random() 慢一点(2 或 3 倍),但我相信它与用 JavaScript 编写的任何其他解决方案一样快。
评论
Antti Sykäri 的算法很好,很短。我最初做了一个变体,在你调用时替换了 JavaScript 的,但后来 Jason 评论说返回该函数会更好:Math.random
Math.seed(s)
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.random
Math.seed(42);
var random = Math.seed(42); random(); random();
0.70...
0.38...
var random = Math.seed(42);
random()
0.70...
0.38...
random
Math.random
结合前面的一些答案,这是您正在寻找的可种子随机函数:
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();
评论
Math.seed(0)()
0.2322845458984375
Math.seed(1)()
0.23228873685002327
m_w
m_z
var m_w = 987654321 + s; var m_z = 123456789 - s;
>>> 0
& mask
编写自己的伪随机生成器非常简单。
戴夫·斯科特斯(Dave Scotese)的建议是有用的,但正如其他人所指出的,它的分布并不完全均匀。
然而,这并不是因为罪的整数论证。这仅仅是因为罪的范围,而罪恰好是一个圆的一维投影。如果你取圆的角度,它将是均匀的。
所以而不是使用.sin(x)
arg(exp(i * x)) / (2 * PI)
如果您不喜欢线性顺序,请将其与 xor 混合一点。实际因素也没那么重要。
要生成 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的作品,可以追溯到1980年代末和1990年代初。还有其他的。如果您不是专家,自己创建一个(伪)随机数生成器是非常危险的,因为结果很可能不是统计随机的,或者周期很短。Pierre(和其他人)已经把一些很好的(伪)随机数生成器放在一起,这些生成器很容易实现。我使用他的一台LFSR发电机。
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
评论
不,不可能播种。ECMAScript 规范在这个主题上故意含糊不清,没有提供播种的方法,甚至不要求浏览器使用相同的算法。因此,这样的功能必须由外部提供,值得庆幸的是,这并不难。Math.random()
我已经在纯 JavaScript 中实现了许多良好、简短和快速的伪随机数生成器 (PRNG) 函数。所有这些都可以播种并提供高质量的数字。这些不是出于安全目的 - 如果您需要可播种的 CSPRNG,请查看 ISAAC。
首先,注意正确初始化您的 PRNG。为简单起见,下面的生成器没有内置的种子生成过程,但接受一个或多个 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);
h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1;
return [h1>>>0, h2>>>0, h3>>>0, h4>>>0];
}
调用将从字符串生成一个 128 位哈希值,该值可用于为 PRNG 设定种子。以下是您可以使用它的方法:cyrb128
// 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)之间的浮点数,相当于 ,如果您想要特定范围的随机数,请阅读这篇关于 MDN 的文章。如果您只想要原始位,只需删除最终的除法操作即可。Math.random()
JavaScript 数字只能表示分辨率高达 53 位的整数。当使用按位运算时,这将减少到 32。其他语言的现代 PRNG 通常使用 64 位操作,这在移植到 JS 时需要填充码,这会大大降低性能。这里的算法只使用 32 位操作,因为它直接兼容 JS。
现在,继续讨论发电机。(我在这里维护带有参考资料和许可证信息的完整列表)
sfc32 (简单快速计数器)
sfc32 是 PractRand 随机数测试套件的一部分(当然它通过了)。sfc32 具有 128 位状态,在 JS 中非常快。
function sfc32(a, b, c, d) {
return function() {
a |= 0; b |= 0; c |= 0; d |= 0;
var t = (a + b | 0) + d | 0;
d = d + 1 | 0;
a = b ^ b >>> 9;
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
c = c + t | 0;
return (t >>> 0) / 4294967296;
}
}
您可能想知道 AND 是干什么用的。这些本质上是 32 位整数强制转换,用于性能优化。 在 JS 中基本上是浮点数,但在按位运算期间,它们切换到 32 位整数模式。JS 解释器处理此模式的速度更快,但任何乘法或加法都会导致它切换回浮点数,从而导致性能下降。| 0
>>>= 0
Number
桑树32
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** 是 Vigna & Blackman 的 Xorshift 家族的新成员(Vigna 教授还负责 Xorshift128+ 算法,该算法为大多数实现提供动力)。它是提供 128 位状态的最快生成器。Math.random
function xoshiro128ss(a, b, c, d) {
return function() {
var t = b << 9, r = b * 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”,他还制作了 ISAAC 和 SpookyHash。它通过了 PractRand 测试,应该非常快,尽管不如 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;
}
}
评论
seed = (seed * 185852 + 1) % 34359738337
Math.imul
如今,许多需要 Javascript 中可播种随机数生成器的人都在使用 David Bau 的 seedrandom 模块。
对于介于 0 和 100 之间的数字。
Number.parseInt(Math.floor(Math.random() * 100))
评论
Math.random
Math.random
Math.random
我编写了一个返回种子随机数的函数,它使用 Math.sin 获得一个长随机数,并使用种子从中挑选数字。
用:
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
不,但 RAN 库解决了这个问题。它几乎具有您能想象到的所有分布,并支持种子随机数生成。例:
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中,有一个函数可以为特定的种子生成固定的随机值。
但是,在 JS 中,没有这样的内置函数。srand(seed)
但是,我们可以编写简单而简短的函数。
第 1 步:选择一些种子(固定编号)。
Number 应为正整数且大于 1,在步骤 2 中进一步说明。var seed = 100;
第 2 步:在 Seed 上执行 Math.sin() 函数,它将给出该数字的 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 步:通过数学的 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)
解释结束。
最终代码
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;
}
然后使用
any_number is must 且 should 大于 1 调用此函数。
number_of_digits 是可选参数,如果未传递任何参数,则返回 1 位数字。random_seed(any_number, number_of_digits)
random_seed(555); // 1 Digit
random_seed(234, 1); // 1 Digit
random_seed(7895656, 1000); // 4 Digit
评论
这里的大多数答案都会产生有偏见的结果。所以这里有一个基于 github 的 seedrandom 库的测试函数:
!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.random 函数提供种子,但可以在 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 的这篇论文,了解上述实现中使用的参数: https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf
无论你做什么,都要避免使用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包
假设种子相同,这两段代码具有相同的输出:
爪哇岛:
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);
}
SIN(id + seed) 是一个非常有趣的 RANDOM 函数的替代品,它不能像 SQLite 那样被植入:
https://stackoverflow.com/a/75089040/7776828
按照 bryc 的建议去做......但在使用他的 cyrb128 哈希函数进行初始化之前,请注意 return 语句丢弃了 32 位熵。Exclusive-or 四个值一起 = 0。您可能应该将第一个元素 (h2^h3^h4) 设>>> 0。
评论