在 Javascript 中播种随机数生成器
Posted
技术标签:
【中文标题】在 Javascript 中播种随机数生成器【英文标题】:Seeding the random number generator in Javascript 【发布时间】:2010-10-05 23:56:52 【问题描述】:是否可以在 javascript 中播种随机数生成器 (Math.random
)?
【问题讨论】:
目前尚不清楚您是要播种它以便为不同的测试运行重复获得相同的结果,还是要为每个用户播种“独特的东西”以获得更好的使用随机性。 不,很遗憾这是不可能的。 jsrand 是我在需要可播种 PRNG 时编写的一个小库。还有其他更复杂的库,您可以通过谷歌搜索找到它。 补充问题:提供 PRNG 而没有播种的方法怎么可能是个好主意?这有什么好的理由吗? 另见***.com/questions/424292 @Alan 我认为可能没有种子,因为底层算法取决于浏览器 - 如果 Math.random() 确实有种子,则不能保证种子在不同的情况下给出相同的结果浏览器。 hackernoon.com/… 【参考方案1】:不,不可能播种Math.random()
,但编写自己的生成器相当容易,或者更好的是,使用现有的生成器。
查看:this related question。
另外,请参阅 David Bau 的博客more information on seeding。
【讨论】:
【参考方案2】:不,但这是一个简单的伪随机生成器,Multiply-with-carry 的实现,我改编自 Wikipedia(此后已被删除):
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;
【讨论】:
有人测试过这个函数的随机性吗? 这是multiply-with-carry (MWC) 随机生成器,周期相当长。改编自wikipedia Random Number Generators 当我将它与我的随机颜色生成器 (HSL) 一起使用时,它只生成绿色和青色。原始随机生成器生成所有颜色。因此,它不一样或不起作用。 @Michael_Scharf 1) 种子更改m_w
,而不是m_z
。 2) m_w
和 m_z
都是根据它们以前的值更改的,所以它确实修改了结果。
当我使用这段代码时,我没有得到分布良好的结果。无论种子如何,输出序列都非常相似。这对我的游戏没有帮助。【参考方案3】:
注意:尽管(或者更确切地说,因为)简洁和明显的优雅,这个算法在随机性方面绝不是一个高质量的算法。寻找例如this answer 中列出的那些以获得更好的结果。
(最初改编自对另一个答案的评论中提出的聪明想法。)
var seed = 1;
function random()
var x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
您可以将seed
设置为任意数字,但要避免零(或 Math.PI 的任意倍数)。
在我看来,这个解决方案的优雅之处在于缺少任何“神奇”数字(除了 10000,它表示您必须丢弃以避免奇数模式的最少数字数量 - 请参阅值 @987654322 的结果@、100、1000)。简洁也不错。
它比 Math.random() 慢一点(2 或 3 倍),但我相信它与任何其他用 JavaScript 编写的解决方案一样快。
【讨论】:
有没有办法证明这个 RNG 生成的数字是均匀分布的?实验上似乎是:jsfiddle.net/bhrLT 6,000,000 ops/second 非常快,我不打算每次点击产生超过约 3,000,000 个。开玩笑,这太棒了。 -1,这根本不是一个统一的采样器——它非常偏向于 0 和 1(请参阅jsfiddle.net/bhrLT/17,这可能需要一段时间来计算)。连续的值是相关的——每 355 个值,甚至每 710 个值都是相关的。请使用更深思熟虑的东西! 问题不是关于创建一个加密安全的随机数生成器,而是在 javascript 中工作的东西,对于快速演示等很有用。我将采取一些快速简单的方法,在百万个随机数用于此目的。 小心。 Math.sin() 可以在客户端和服务器上给出不同的结果。我使用 Meteor(在客户端和服务器上使用 javascript)。【参考方案4】: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.random
,那将允许你拥有多个独立的生成器,对吧?
如果这对您很重要,请务必查看上面关于随机性分布的 cmets:***.com/questions/521295/…
由此产生的随机数如何重复?它每次都会给出新的数字
每次你做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
的局部变量,而不是覆盖本机 javascript 函数。覆盖 Math.random
可能会导致 JIST 编译器未优化您的所有代码。【参考方案5】:
结合之前的一些答案,这就是您正在寻找的可种子随机函数:
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;
产生具有不同种子的第一个值的良好分布。
@undefined 您描述的问题在上次编辑时已修复,这是 MWC 实现中的错误。
截至 2020 年 1 月,现在运行良好。种子为 0,得到 0.7322976540308446。种子含 1,0.16818441334180534,含 2:0.6040864314418286,含 3:0.03998844954185188。谢谢你们!【参考方案6】:
编写自己的伪随机生成器非常简单。
Dave Scotese 的建议很有用,但正如其他人所指出的,它的分布并不十分均匀。
然而,这不是因为 sin 的整数参数。这只是因为罪的范围,恰好是一个圆的一维投影。如果你取圆的角度,它会是均匀的。
所以用 arg(exp(i * x)) / (2 * PI) 代替 sin(x)。
如果您不喜欢线性顺序,请将其与 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))
还请注意,当需要真实熵时,您不能使用伪随机序列。
【讨论】:
我不是专家,但顺序种子遵循constant pattern。彩色像素 >= 0.5。我猜它只是一遍又一遍地迭代半径?【参考方案7】:请查看 Pierre L'Ecuyer 可追溯到 1980 年代末和 1990 年代初的作品。还有其他的。如果您不是专家,那么您自己创建一个(伪)随机数生成器是非常危险的,因为结果很可能不是统计上随机的,或者周期很短。 Pierre(和其他人)已经组合了一些易于实现的好(伪)随机数生成器。我使用他的一台 LFSR 发生器。
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
菲尔·特洛伊
【讨论】:
很好的答案,但与 javascript 无关 :) 实现 L'Ecuyer 教授工作的代码是公开的,可用于 java,并且可以被大多数程序员轻松翻译成 Javascript。【参考方案8】:我已经用纯 JavaScript 实现了许多优秀、简短和快速的伪随机数生成器 (PRNG) 函数。所有这些都可以播种并提供高质量的数字。
首先,注意正确初始化您的 PRNG。 为简单起见,下面的生成器没有内置种子生成过程,但接受一个或多个 32 位值作为PRNG 的初始种子状态。相似或稀疏的种子(例如 1 和 2 的简单种子)具有低熵,并且可能导致相关性或其他质量问题,从而导致输出具有相似的属性(例如随机生成的级别相似)。为避免这种情况,最佳做法是使用分布良好的高熵种子初始化 PRNG。
值得庆幸的是,哈希函数非常擅长从短字符串生成种子。即使两个字符串相似,一个好的哈希函数也会产生非常不同的结果。下面是一个基于 MurmurHash3 混合函数的种子生成器示例:
function xmur3(str)
for(var i = 0, h = 1779033703 ^ str.length; i < str.length; i++)
h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
h = h << 13 | h >>> 19;
return function()
h = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return (h ^= h >>> 16) >>> 0;
对xmur3
的返回函数 的每次后续调用都会生成一个新的32 位哈希值,用作PRNG 中的种子。以下是您可以如何使用它:
// Create xmur3 state:
var seed = xmur3("apples");
// Output four 32-bit hashes to provide the seed for sfc32.
var rand = sfc32(seed(), seed(), seed(), seed());
// Output one 32-bit hash to provide the seed for mulberry32.
var rand = mulberry32(seed());
// Obtain sequential random numbers like so:
rand();
rand();
但是,这只是一种可能的解决方案。或者,只需选择一些虚拟数据来填充种子,然后将生成器推进几次(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()
,如果你想要特定范围的随机数,请阅读this article on MDN。如果您只想要原始位,只需删除最后的除法操作即可。
注意:JavaScript 数字只能表示分辨率高达 53 位的整数。而当使用按位运算时,这会减少到 32 个。其他语言中的现代 PRNG 通常使用 64 位运算,这在移植到 JS 时需要 shims,这会大大降低性能。这里的算法只使用 32 位运算,因为它直接兼容 JS。
现在,继续讨论生成器。 (我维护完整列表以及参考和许可证信息here)
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;
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;
桑树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 并且不需要数十亿个随机数(请参阅Birthday problem),我会推荐这个。
xoshiro128**
截至 2018 年 5 月,xoshiro128** 是 Vigna & Blackman 的 Xorshift family 的新成员(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;
作者声称它很好地通过了随机性测试 (albeit with caveats)。其他研究人员指出,它未能通过 TestU01 中的一些测试(尤其是 LinearComp 和 BinaryRank)。在实践中,使用浮点数时应该不会导致问题(例如这些实现),但如果依赖原始低位可能会导致问题。
JSF(詹金斯的小快)
这是 Bob Jenkins (2007) 的 JSF 或“smallprng”,他还制作了 ISAAC 和 SpookyHash。它passes 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;
【讨论】:
我相信您从 Pierre L'ecuyer 的“线性同余生成器表...”中引用的值可能会超过 Javascript 中的最大整数大小。 (2^32-1) * 741103597 ≈ 3e18 的最大种子,大于 JavaScript 的最大 int 大小 ≈ 9e15。我认为 Pierre 书中的以下值在本地范围内具有最大的周期:seed = (seed * 185852 + 1) % 34359738337
。
@Lachmanski 是的,但这些受 32 位(和 Park-Miller 31 位)的约束。使用Math.imul
允许它溢出,就像在 C 中对 32 位整数使用乘法一样。您所建议的是使用 JS 整数空间的全部范围的 LCG,这绝对是一个值得探索的有趣领域。 :)
这太棒了!我可以将您的 sfc32 复制到 LGPL 程序中吗?
当然,您可以随意将代码用于任何目的:)
@blobber2 不确定您的意思,但原始代码来自此处(与其他人一起):github.com/bryc/code/blob/master/jshash/PRNGs.md。或多或少是存储库中的要点:-)【参考方案9】:
如今,许多需要在 Javascript 中使用可种子随机数生成器的人都在使用 David Bau's seedrandom module。
【讨论】:
【参考方案10】:对于 0 到 100 之间的数字。
Number.parseInt(Math.floor(Math.random() * 100))
【讨论】:
问题是关于播种Math.random
,这样每当Math.random
播种相同的种子时,它将产生相同的连续随机数序列。据说,这个问题与Math.random
的实际使用/演示无关。【参考方案11】:
我编写了一个返回种子随机数的函数,它使用 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)
【讨论】:
由此产生的数字序列并不能真正近似随机数序列的性质。用它生成 15 个数字,例如,对于几乎任何键,生成的字符串几乎总是以 7 开头。【参考方案12】:固定种子的简单方法:
function fixedrandom(p)
const seed = 43758.5453123;
return (Math.abs(Math.sin(p)) * seed)%1;
【讨论】:
【参考方案13】:Math.random
没有,但是ran library 解决了这个问题。它几乎具有您可以想象的所有分布,并支持种子随机数生成。示例:
ran.core.seed(0)
myDist = new ran.Dist.Uniform(0, 1)
samples = myDist.sample(1000)
【讨论】:
【参考方案14】:这是采用的 Jenkins 哈希版本,借用自 here
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
【讨论】:
【参考方案15】:在 php 中,有一个函数 srand(seed)
为特定种子生成固定的随机值。
但是,在 JS 中,并没有这样的内置函数。
但是,我们可以编写简单而简短的函数。
步骤1:选择一些种子(修复编号)。 var seed = 100;
数字应为正整数且大于 1,步骤 2 中有进一步说明。
步骤2:在Seed上执行Math.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)
解释结束。最终代码
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;
然后调用这个函数使用random_seed(any_number, number_of_digits)
any_number 必须大于 1。number_of_digits 是可选参数,如果没有传递,则返回 1 位。
random_seed(555); // 1 Digit
random_seed(234, 1); // 1 Digit
random_seed(7895656, 1000); // 4 Digit
【讨论】:
这个函数是有偏的,因为 abs(Math.sin(random_number)) 本身就是一个偏向于 sin 函数斜率为零的点的函数。下面是可以在js控制台运行实验的代码:(pastebin.com/kJyHaQYY)【参考方案16】:这里的大多数答案都会产生有偏见的结果。所以这里有一个基于seedrandom library from 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)/tvar o=[],i=j(function n(t,r)var e,o=[],i=typeof t;if(r&&"object"==i)for(e in t)tryo.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()tryvar 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(),tfunction 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;trys=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/
【讨论】:
以上是关于在 Javascript 中播种随机数生成器的主要内容,如果未能解决你的问题,请参考以下文章