当前位置:  开发笔记 > 编程语言 > 正文

在Javascript中播种随机数生成器

如何解决《在Javascript中播种随机数生成器》经验,为你挑选了6个好方法。

是否可以在Javascript中播种随机数生成器(Math.random)?



1> PeterAllenWe..:

不,它不是,但是编写自己的生成器相当容易,或者更好地使用现有的生成器.退房:这个相关的问题.

另外,请参阅David Bau的博客,了解有关播种的更多信息.



2> Antti Kissan..:

注意:尽管(或者更确切地说,由于)简洁和明显的优雅,这种算法在随机性方面绝不是高质量的算法.寻找例如本答案中列出的那些以获得更好的结果.

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

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编写的任何其他解决方案一样快.


-1,这根本不是一个统一的采样器 - 它偏向于0和1(参见http://jsfiddle.net/bhrLT/17/,可能需要一段时间才能计算).连续的值是相关的 - 每355个值,甚至更多,每710个相关.请仔细考虑使用一些东西!
问题不在于创建一个加密安全的随机数生成器,而是在javascript中工作,对快速演示等有用.我会采取快速简单的方法,为此目的提供超过一百万个随机数的良好分布.
有没有办法证明这个RNG生成均匀分布的数字?实验上似乎:http://jsfiddle.net/bhrLT/
小心.Math.sin()可以在客户端和服务器上提供不同的结果.我使用Meteor(在客户端和服务器上使用javascript).
[6,000,000 ops /秒](http://jsperf.com/math-random-vs-seeded-rand)非常快,我不计划每次点击产生超过3,000,000.开玩笑,这很棒.
看一下尼尔森直方图发布的直方图,看起来很容易通过丢弃范围之外的结果来改善均匀性,比如[0.1,0.9],并将该范围内的剩余结果归一化为[0,1].
Jason Goemaat:实际上,在这种情况下,任何mod操作都可以解决问题,为此,仅使用sin(x)或mod(x,...)是相同的。这不是随机数,您还可以使用置换来提高速度。如果接受这种技巧,那么这个问题就会产生误导。我正在投票。随机生成器应该显示任何相关性,也不显示频率模式,这很愚蠢。
根本不回答这个问题,是不对的。

3> bryc..:

我已经在纯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,但它可以被客观化.

向货物(发电机)前进.


SFC32

这个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

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;
    }
}

xoshiro128**

截至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一样.但是,如果依赖于低位,则可能会导致问题.

JSF

这是由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位种子的循环长度.

LCG(又名Lehmer/Park-Miller RNG或MLCG)

这只是为了提供一个更好的替代方案,以替代其他答案中提到的选项,例如xmur3Math.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日的不同尺寸和良好晶格结构的线性同余发电机表.


这是一个惊人的答案.我肯定会回到这里.

4> Antti Kissan..:

不,但是这里是一个简单的伪随机生成器,一个从维基百科改编而来的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:严重的实施缺陷已得到修复


`seed`函数不会重置随机生成器,因为调用`random()`时会改变`mz_z`变量.因此在`seed`中设置`mz_z = 987654321`(或任何其他值)
有没有人测试过这个函数的随机性?
这是具有相当长时间的[乘法携带(MWC)](http://en.wikipedia.org/wiki/Multiply-with-carry)随机生成器.改编自[wikipedia随机数生成器](http://en.wikipedia.org/wiki/Random_number_generation#Computational_methods)

5> Remco Kranen..:

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不具备的另一项功能:多个独立的随机生成器.如果您希望同时运行多个可重复的模拟,这一点尤为重要.


如果你返回函数而不是设置允许你有多个独立生成器的`Math.random`,对吧?

6> user2383235..:

请参阅Pierre L'Ecuyer的工作,可追溯到20世纪80年代末和90年代初.还有其他人.如果您不是专家,自己创建一个(伪)随机数生成器是非常危险的,因为很可能结果不是统计随机的或者有一个小周期.皮埃尔(和其他人)已经将一些易于实现的好(伪)随机数生成器组合在一起.我使用他的一个LFSR发电机.

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

菲尔特洛伊


实现L'Ecuyer教授作品的代码可以公开获取,并且可以被大多数程序员轻松翻译成Javascript.
推荐阅读
pan2502851807
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有