在 Javascript 中播种随机数生成器

新手上路,请多包涵

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

原文由 weepy 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答

不,不可能播种 Math.random() ,但是编写自己的生成器相当容易,或者更好的是,使用现有的生成器。

退房: 这个相关的问题

此外, 有关播种的更多信息,请参阅 David Bau 的博客。

原文由 PeterAllenWebb 发布,翻译遵循 CC BY-SA 4.0 许可协议

不,不可能播种 Math.random()ECMAScript 规范 在这个主题上故意含糊其辞,既不提供播种方式,也不要求浏览器使用相同的算法。所以必须要外部提供这样的功能,还好不是太难。

我已经在纯 JavaScript 中实现了许多好的、简短且快速的 伪随机数生成器(PRNG) 函数。所有这些都可以播种并提供高质量的数字。这些不是出于安全目的——如果您需要可播种的 CSPRNG,请查看 ISAAC

首先,注意正确初始化 PRNG。 为了简单起见,下面的生成器没有内置的种子生成过程,而是接受一个或多个 32 位数字作为 PRNG 的初始 _种子状态_。相似或稀疏种子(例如 1 和 2 的简单种子)具有低熵,并可能导致相关性或其他随机质量问题,有时会导致输出具有相似的属性(例如随机生成的级别相似)。为避免这种情况,最佳做法是使用分布良好的高熵种子和/或前进超过前 15 个左右的数字来初始化 PRNG。

有很多方法可以做到这一点,但这里有两种方法。首先, 哈希函数 非常擅长从短字符串生成种子。即使两个字符串相似,一个好的散列函数也会产生截然不同的结果,因此您不必对字符串花太多心思。这是一个示例哈希函数:

 function cyrb128(str) {
    let h1 = 1779033703, h2 = 3144134277,
        h3 = 1013904242, h4 = 2773480762;
    for (let i = 0, k; i < str.length; i++) {
        k = str.charCodeAt(i);
        h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
        h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
        h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
        h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
    }
    h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
    h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
    h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
    h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
    return [(h1^h2^h3^h4)>>>0, (h2^h1)>>>0, (h3^h1)>>>0, (h4^h1)>>>0];
}

调用 cyrb128 将从一个字符串中生成一个 128 位哈希值,该值可用于为 PRNG 播种。以下是您可能如何使用它:

 // Create cyrb128 state:
var seed = cyrb128("apples");
// Four 32-bit component hashes provide the seed for sfc32.
var rand = sfc32(seed[0], seed[1], seed[2], seed[3]);

// Only one 32-bit component hash is needed for mulberry32.
var rand = mulberry32(seed[0]);

// Obtain sequential random numbers like so:
rand();
rand();

_注意:如果您想要稍微更健壮的 128 位哈希,请考虑 MurmurHash3_x86_128 ,它更彻底,但适用于大型数组。_

或者,只需选择一些虚拟数据来填充种子,并预先将生成器推进几次(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 到 2 32 -1),然后将其转换为 0-1 之间的浮点数(0 包含,1 不包含)相当于 Math.random() ,如果您想要特定范围的随机数,请阅读 MDN 上的这篇文章。如果您只想要原始位,只需删除最后的除法运算即可。

JavaScript 数字只能表示高达 53 位分辨率的整数。当使用按位运算时,这会减少到 32。其他语言的现代 PRNG 通常使用 64 位运算,这在移植到 JS 时需要垫片,这会 大大 降低性能。这里的算法只使用32位运算,直接兼容JS。

现在,转到发电机。 (我在 此处 维护包含参考和许可信息的完整列表)


sfc32(简单快速计数器)

sfc32PractRand 随机数测试套件的一部分(它当然通过了)。 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;
    }
}

您可能想知道 | 0>>>= 0 的用途。这些本质上是 32 位整数转换,用于性能优化。 Number 在JS中基本都是浮点数,但在按位运算时,会切换到32位整数模式。 JS 解释器处理此模式的速度更快,但任何乘法或加法都会导致它切换回浮点数,从而导致性能下降。

桑32

Mulberry32 是一个简单的 32 位状态生成器,但速度极快且具有良好的随机性(作者说它通过了 gjrand 测试套件的所有测试并且具有完整的 2 32周期,但我尚未验证)。

 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 而不需要数十亿个随机数(请参阅 生日问题),我会推荐这个。

xoshiro128**

截至 2018 年 5 月, xoshiro128** 是 Vigna 和 Blackman 的 Xorshift 家族 的新成员(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;
    }
}

作者声称它很好地通过了随机性测试( 尽管有警告)。其他研究人员指出,它未能通过 TestU01 中的某些测试(特别是 LinearComp 和 BinaryRank)。在实践中,使用浮点数时(例如在这些实现中)应该不会引起问题,但如果依赖原始最低位可能会引起问题。

JSF(詹金斯的小快)

这是 Bob Jenkins(2007 年)的 JSF 或“smallprng”,他还制作了 ISAACSpookyHash 。它 通过 了 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;
    }
}

原文由 bryc 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题