Seeding the random number generator in Javascript
是否可以在javascript中植入随机数生成器(math.random)?
不,它不是,但编写自己的生成器相当容易,或者最好使用现有的生成器。退房:这个相关的问题。
此外,有关播种的更多信息,请参阅DavidBau的博客。
注意:尽管(或者更确切地说,由于)简洁和明显的优雅,但从随机性来看,该算法决不是高质量的。例如,寻找本答案中列出的内容,以获得更好的结果。
(最初改编自对另一个答案的评论中提出的一个聪明的想法。)
1 2 3 4 5 | var seed = 1; function random() { var x = Math.sin(seed++) * 10000; return x - Math.floor(x); } |
您可以将
在我看来,这个解决方案的优雅之处在于缺少任何"神奇"数字(除了10000,它代表了为避免出现奇怪的模式而必须丢弃的最小数字量——参见值为10、100、1000的结果)。简洁也不错。
它比math.random()慢一点(2或3倍),但我相信它和用javascript编写的其他解决方案一样快。
我已经用简单的javascript实现了许多好的、短的和快速复制的可通吃的prng函数。所有这些都可以播种并提供良好的质量数字。好的。
首先,注意正确初始化您的prng。下面的大多数生成器没有内置的种子生成过程,但接受一个或多个32位值作为prng的初始状态。相似的种子(例如1号和2号种子)可能导致弱prng的相关性,从而导致具有相似特性的输出(例如随机产生的水平相似)。为了避免这种情况,最好使用分布良好的种子初始化prng。好的。
幸运的是,哈希函数非常擅长从短字符串中为prng生成种子。即使两个字符串相似,一个好的哈希函数也会产生非常不同的结果。下面是一个基于杂音3混合函数的例子:好的。
1 2 3 4 5 6 7 8 9 10 | 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; } } |
对返回函数的每个后续调用都会生成一个新的"随机"32位散列值,用作prng中的种子。以下是您可以使用它的方法:好的。
1 2 3 4 5 6 7 8 9 10 11 | // 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(); |
这当然是功能性的JS,但是它可以被对象化。好的。
向货物(发电机)前进。好的。SFC32
这个gem来自practrandrandom-number测试套件,它可以毫无问题地通过测试。Practrand据说比testu01更严格。sfc32具有128位状态,在JS中也非常快(xoshiro128**速度稍快,但质量较差)。这可能是我的选择。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } } |
MulbRy32
桑椹32也很快,而且质量很好(作者说它通过了GJRAND的所有测试)。如果你只是需要一个简单但体面的prng,我会推荐这个。好的。
它有32位的状态和232的完整周期。如果您只想使用一个32位整数进行种子植入,并且不关心生日问题,那么这是理想的选择。与SFC32/XOShiro128*中的340个不确定状态相比,桑树32中可能存在43亿个状态。好的。
1 2 3 4 5 6 7 8 | 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; } } |
XOHIHRO128**
截至2018年5月,Xorshiro128**是Xorshift家族的新成员。它提供128位的状态,而且速度非常快。好的。
1 2 3 4 5 6 7 8 9 | 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; } } |
这个prng是blackman/vigna的最新版本,他也写了prng xorshift128+和xoroshiro,这些都是2015年在谷歌Chrome上使用的。值得注意的是,它是为数不多的具有32位版本的现代prng之一。Xoroshiro64**也是一个很有前途的选项,但它只有64位状态,并且大部分被Xoshiro所取代。好的。
作者声称它很好地通过了随机性测试(尽管有警告)。其他研究人员指出,在BigCrush(特别是LinearComp和BinaryRank)中,有些测试失败。但在实践中并不重要,特别是如果32位值转换为0-1之间的浮点值,就像这些prng一样。但是,如果您依赖低阶位,则可能会导致问题。好的。JSF
这是BobJenkins(2007)的JSF或"smallprng",他制造了Isaac和Spokyhash。它在practrand测试中表现良好,应该非常快。平均周期长度假定为2^126,但"尚未正式确定"。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | function JSF(seed) { function jsf() { var e = s[0] - (s[1]<<27 | s[1]>>>5); s[0] = s[1] ^ (s[2]<<17 | s[2]>>>15), s[1] = s[2] + s[3], s[2] = s[3] + e, s[3] = s[0] + e; return (s[3] >>> 0) / 4294967296; // 2^32 } seed >>>= 0; var s = [0xf1ea5eed, seed, seed, seed]; for(var i=0;i<20;i++) jsf(); return jsf; } |
此版本不需要单独的种子函数。但结果是,只能输入32位,并且这个版本预运行JSF()20次以分散初始状态,这可能会很昂贵。好的。
如果需要,可以直接初始化整个128位状态并删除for循环。我决定保留原来的结构,因为作者验证了给定配置中每个可能的32位种子的周期长度。好的。LCG(又名Lehmer/Park Miller RNG或MLCG)
在这里,这只是为了提供比其他答案中提到的选项更好的选择,如
1 | var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31; |
这是Park—Miller在1988和1993中提出的最小标准RNG,在C++ 11中实现为EDCOX1×2。请记住,状态和周期仅为31位(31位表示20亿个可能的状态,32位表示双倍的状态)。这正是其他人试图取代的PRNG类型。好的。
它可以工作,但我不会使用它,除非你真的需要速度,不关心随机性质量(什么是随机的?)或31位状态/周期大小。非常适合游戏果酱、演示或其他东西。此外,LCG也存在种子相关性,因此最好放弃LCG的第一个结果。好的。
似乎还有其他乘法器提供完整的32位状态。我不知道这些数据是否比Park Miller的好/坏,但这里是为了完整性。好的。
1 2 | var LCG=s=>()=>((s=Math.imul(741103597,s))>>>0)/2**32; var LCG=s=>()=>((s=Math.imul(1597334677,s))>>>0)/2**32; |
这些乘数来自:P.L'Ecuyer:不同尺寸和良好晶格结构的线性同余生成器表,1997年4月30日。好的。好啊。
不,但这里有一个简单的伪随机生成器,这是我从维基百科改编的乘进位的实现(从那时起就被删除了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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_z来固定种子函数edit2:修复了严重的实施缺陷
Antti Syk?Ri的算法又好又短。最初我做了一个变体,在调用math.seed时替换了javascript的math.random,但是Jason评论说返回函数会更好:
1 2 3 4 5 6 7 8 9 10 | 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不具备的功能:多个独立的随机生成器。如果您希望同时运行多个可重复的模拟,这一点尤其重要。
请看皮埃尔·勒库耶的作品,可以追溯到20世纪80年代末和90年代初,还有其他的作品。如果您不是专家,那么自己创建一个(伪)随机数生成器是非常危险的,因为很有可能结果不是统计上的随机数,或者有一个很小的周期。皮埃尔(和其他人)已经组装了一些好的(伪)随机数生成器,这些生成器很容易实现。我用他的一个LFSR发电机。
网址:https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
菲尔特洛伊
现在,许多需要在javascript中使用可种子随机数生成器的人正在使用david bau的seedrandom模块。
编写自己的伪随机生成器非常简单。
戴夫·斯科茨的建议是有用的,但正如其他人指出的,它并不是均匀分布的。
然而,这并不是因为sin的整型参数。这仅仅是因为罪恶的范围,恰好是一个圆的一维投影。如果你用圆的角度来代替,它将是均匀的。
因此,使用arg(exp(i*x))/(2*pi)代替sin(x)。
如果您不喜欢线性顺序,可以将其与XOR混合使用。实际因素也没那么重要。
要生成n个伪随机数,可以使用以下代码:
1 2 3 4 5 | 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)) |
请注意,当需要真正的熵时,不能使用伪随机序列。
结合前面的一些答案,这是您要查找的可种子随机函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); |
1 2 3 | ran.core.seed(0) myDist = new ran.Dist.Uniform(0, 1) samples = myDist.sample(1000) |
我编写了一个函数,它返回一个种子随机数,它使用math.sin获得一个长的随机数,并使用种子从中选取数字。
用途:
1 | seedRandom("k9]:2@", 15) |
它将返回您的种子编号第一个参数是任何字符串值;您的种子。第二个参数是返回的位数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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) } |
固定种子的简单方法:
1 2 3 4 | function fixedrandom(p){ const seed = 43758.5453123; return (Math.abs(Math.sin(p)) * seed)%1; } |
对于介于0和100之间的数字。
1 | Number.parseInt(Math.floor(Math.random() * 100)) |