在Javascript中播种随机数生成器

wee*_*epy 337 javascript random

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

Pet*_*ebb 176

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

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


Ant*_*emi 149

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

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

var seed = 1;
function random() {
    var x = Math.sin(seed++) * 10000;
    return x - Math.floor(x);
}
Run Code Online (Sandbox Code Playgroud)

您可以设置seed为任意数字,只需避免零(或Math.PI的任何倍数).

该解决方案的优雅,在我看来,来自于没有任何"神奇"号(除了10000的,这代表你必须扔掉,以避免单模式数字的最小量-看到值结果10,100,1000).简洁也很好.

它比Math.random()稍微慢一点(比例为2或3),但我相信它与用JavaScript编写的任何其他解决方案一样快.

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

bry*_*ryc 93

我已经在纯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;
    }
}
Run Code Online (Sandbox Code Playgroud)

对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();
Run Code Online (Sandbox Code Playgroud)

这当然是功能性的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这PRNG是最新的布莱克曼/豇豆谁也写在谷歌浏览器中使用回,在2015年值得注意的是作为为数不多的现代的PRNG与32位版本之一的PRNG xorshift128 +和xoroshiro.xoroshiro64**也是一个很有前途的选择,但只有64位状态,并且已经被xoshiro取代.

作者声称它很好地通过了随机性测试(尽管有警告).其他研究人员指出,在BigCrush中失败了一些测试(特别是LinearComp和BinaryRank).但实际上并不重要,特别是如果将32位值转换为0-1之间的浮点数,就像这些PRNG一样.但是,如果依赖于低位,则可能会导致问题.

JSF

这是由Bob Jenkins(2007)制作的JSF或'smallprng',他是制作ISAACSpookyHash的人.它在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;
}
Run Code Online (Sandbox Code Playgroud)

此版本不需要单独的种子功能.但结果是,只有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;
Run Code Online (Sandbox Code Playgroud)

这是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;
Run Code Online (Sandbox Code Playgroud)

这些乘数来自:P.L'Ecuyer:1997年4月30日的不同尺寸和良好晶格结构的线性同余发电机表.

  • @blobber2 不确定你的意思,但原始代码来自这里(与其他人一起):https://github.com/bryc/code/blob/master/jshash/PRNGs.md。或多或少是存储库中的要点:-) (4认同)
  • 这是一个惊人的答案.我肯定会回到这里. (3认同)
  • @Lachmanski 是的,但这些受 32 位(和 Park-Miller 31 位)的约束。使用 `Math.imul` 会导致溢出,就像在 C 中对 32 位整数使用乘法时一样。你建议的是一个利用 JS 整数空间全部范围的 LCG,这绝对是一个值得探索的有趣领域。:) (2认同)
  • 这太棒了!我可以将您的 sfc32 复制到 LGPL 程序中吗? (2认同)
  • 当然,无论出于什么目的,请随意使用该代码:) (2认同)

Ant*_*emi 38

不,但是这里是一个简单的伪随机生成器,一个从维基百科改编而来的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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:修复种子功能,使其重置m_z
EDIT2:严重的实施缺陷已得到修复

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

Rem*_*urg 25

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());
Run Code Online (Sandbox Code Playgroud)

这为您提供了Javascript不具备的另一项功能:多个独立的随机生成器.如果您希望同时运行多个可重复的模拟,这一点尤为重要.

  • 如果你返回函数而不是设置允许你有多个独立生成器的`Math.random`,对吧? (3认同)
  • 请不要使用这个。请花点时间改用名为“random”的局部变量,而不是覆盖原生 javascript 函数。覆盖 `Math.random` 可能会导致 JIST 编译器未优化所有代码。 (3认同)
  • 如果这对您很重要,请务必查看上面有关随机性分布的评论:http://stackoverflow.com/questions/521295/javascript-random-seeds#comment36604417_19303725 (2认同)

use*_*235 11

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

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

菲尔特洛伊

  • 很好的答案,但与 javascript 无关:) (3认同)
  • 实现L'Ecuyer教授作品的代码可以公开获取,并且可以被大多数程序员轻松翻译成Javascript. (3认同)

ccx*_*vii 11

不可能为内置 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())
Run Code Online (Sandbox Code Playgroud)

如果您想要更高质量的随机数,但代价是速度慢约 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
    }
}
Run Code Online (Sandbox Code Playgroud)

有关上述实现中使用的参数,请参阅 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 的所有其他答案!


小智 6

结合之前的一些答案,这是您正在寻找的可种子随机函数:

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();
Run Code Online (Sandbox Code Playgroud)

  • 这在具有不同种子的序列开始时产生非常相似的结果。例如,“Math.seed(0)()”返回“0.2322845458984375”,“Math.seed(1)()”返回“0.23228873685002327”。根据种子更改 `m_w` 和 `m_z` 似乎有帮助。`var m_w = 987654321 + s; var m_z = 123456789 - s;` 产生具有不同种子的第一个值的良好分布。 (4认同)
  • @undefined 您描述的问题在上次编辑时已修复,这是 MWC 实现中的一个错误。 (2认同)

小智 6

编写自己的伪随机生成器非常简单。

戴夫·斯科特斯的建议很有用,但正如其他人指出的那样,它的分布并不十分均匀。

然而,这并不是因为 sin 的整数参数。这只是因为罪的范围,它恰好是圆的一维投影。如果你取圆的角度,它就会是均匀的。

所以而不是sin(x)使用arg(exp(i * x)) / (2 * PI).

如果您不喜欢线性顺序,请将其与异或混合起来。实际因素也没有那么重要。

要生成 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))
Run Code Online (Sandbox Code Playgroud)

另请注意,当需要真实熵时,不能使用伪随机序列。

  • “编写自己的伪随机生成器非常简单。” -- 但编写一个正确的伪随机生成器则不然。 (3认同)