是否可以在Javascript中播种随机数生成器(Math.random)?
不,它不是,但是编写自己的生成器相当容易,或者更好地使用现有的生成器.退房:这个相关的问题.
另外,请参阅David Bau的博客,了解有关播种的更多信息.
注意:尽管(或者更确切地说,由于)简洁和明显的优雅,这种算法在随机性方面绝不是高质量的算法.寻找例如本答案中列出的那些以获得更好的结果.
(最初改编自对另一个答案的评论中提出的一个聪明的想法.)
var seed = 1; function random() { var x = Math.sin(seed++) * 10000; return x - Math.floor(x); }
您可以设置seed
为任意数字,只需避免零(或Math.PI的任何倍数).
该解决方案的优雅,在我看来,来自于没有任何"神奇"号(除了10000的,这代表你必须扔掉,以避免单模式数字的最小量-看到值结果10,100,1000).简洁也很好.
它比Math.random()稍微慢一点(比例为2或3),但我相信它与用JavaScript编写的任何其他解决方案一样快.
我已经在纯JavaScript中实现了许多优秀的,短的和快速的可复制PRNG函数.所有这些都可以播种并提供高质量的数字.
首先,注意正确初始化您的PRNG.下面的大多数生成器没有内置的种子生成过程,但是接受一个或多个32位值作为PRNG 的初始状态.类似的种子(例如1和2的种子)可以导致较弱的PRNG中的相关性,导致输出具有相似的属性(例如随机生成的水平相似).为避免这种情况,最佳做法是使用分布均匀的种子初始化PRNG.
值得庆幸的是,哈希函数非常适合从短字符串为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; } }
对return函数的每次后续调用都会产生一个新的"随机"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();
这当然是功能性的JS,但它可以被客观化.
向货物(发电机)前进.
这个gem来自PractRand随机数测试套件,它可以毫无问题地通过.PractRand据称甚至比TestU01更严格.sfc32具有128位状态,在JS中也非常快(xoshiro128**稍快,但质量更差).这可能是我选择的PRNG.
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; } }
Mulberry32也非常快,质量很好(作者声称它通过了gjrand的所有测试).如果你只需要一个简单但不错的 PRNG,我会推荐这个.
它具有32位的状态和2 32的完整周期.如果你只想用一个32位整数播种并且不关心生日问题,这是理想的选择.Mulberry32中有43亿个可能状态,而sfc32/xoshiro128**则为340个未命中状态.
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; } }
截至2018年5月,xoshiro128**是Xorshift系列的新成员.它提供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; } }
这PRNG是最新的布莱克曼/豇豆谁也写在谷歌浏览器中使用回,在2015年值得注意的是作为为数不多的现代的PRNG与32位版本之一的PRNG xorshift128 +和xoroshiro.xoroshiro64**也是一个很有前途的选择,但只有64位状态,并且已经被xoshiro取代.
作者声称它很好地通过了随机性测试(尽管有警告).其他研究人员指出,在BigCrush中失败了一些测试(特别是LinearComp和BinaryRank).但实际上并不重要,特别是如果将32位值转换为0-1之间的浮点数,就像这些PRNG一样.但是,如果依赖于低位,则可能会导致问题.
这是由Bob Jenkins(2007)制作的JSF或'smallprng',他是制作ISAAC和SpookyHash的人.它在PractRand测试中表现良好,应该非常快.假设平均周期长度为2 ^ 126,但"尚未正式确定".
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位种子的循环长度.
这只是为了提供一个更好的替代方案,以替代其他答案中提到的选项,例如xmur3
或Math.sin
方法,这些方案在不同平台上不太一致或可靠.这种LCG实现非常快,但只有31位状态,并且未能通过前面提到的生成器通过飞行颜色的一些统计测试.虽然这是一个单行 - 这很好:).
var LCG=s=>()=>(2**31-1&(s=Math.imul(48271,s)))/2**31;
这是Park-Miller在1988年和1993年提出的最小标准 RNG,并在C++ 11中实现.请记住,状态和周期仅为31位(31位给出20亿个可能状态,32位给出两倍).这是其他人试图取代的PRNG类型.Math.PI
它会工作,但我不会使用它,除非你真的需要速度而不关心随机性质量(无论是什么是随机的?)或31位状态/周期大小.非常适合游戏果酱或演示或其他东西.此外,LCG受种子相关影响,因此最好丢弃LCG的第一个结果.
似乎有其他乘法器可以提供完整的32位状态.我不知道这些在统计上是否比Park-Miller更好/更差,但在这里它们是完整的.
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日的不同尺寸和良好晶格结构的线性同余发电机表.
不,但是这里是一个简单的伪随机生成器,一个从维基百科改编而来的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; }
编辑:修复种子功能,使其重置m_z
EDIT2:严重的实施缺陷已得到修复
AnttiSykä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不具备的另一项功能:多个独立的随机生成器.如果您希望同时运行多个可重复的模拟,这一点尤为重要.
请参阅Pierre L'Ecuyer的工作,可追溯到20世纪80年代末和90年代初.还有其他人.如果您不是专家,自己创建一个(伪)随机数生成器是非常危险的,因为很可能结果不是统计随机的或者有一个小周期.皮埃尔(和其他人)已经将一些易于实现的好(伪)随机数生成器组合在一起.我使用他的一个LFSR发电机.
https://www.iro.umontreal.ca/~lecuyer/myftp/papers/handstat.pdf
菲尔特洛伊