搜索

首页  >  问答  >  正文

在 Javascript 中播种随机数生成器

<p>是否可以在 JavaScript 中为随机数生成器 (<code>Math.random</code>) 提供种子?</p>
P粉006977956P粉006977956467 天前647

全部回复(2)我来回复

  • P粉482108310

    P粉4821083102023-08-24 22:35:05

    不,不可能播种 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);
        h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1;
        return [h1>>>0, h2>>>0, h3>>>0, h4>>>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 到 232-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 位整数转换,用于性能优化。 JS 中的 Number 基本上都是浮点数,但是在按位运算时,它们会切换到 32 位整数模式。这种模式被 JS 解释器处理得更快,但任何乘法或加法都会导致它切换回浮点数,从而导致性能下降。

    桑树32

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

    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** 的新成员Xorshift 系列,由 Vigna 和 Blackman 开发(Vigna 教授还负责 Xorshift128+ 算法,为大多数 Math.random 实现提供支持)。它是提供 128 位状态的最快生成器。

    function xoshiro128ss(a, b, c, d) {
        return function() {
            var t = b << 9, r = b * 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”。 和 幽灵哈希。它通过 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;
        }
    }

    回复
    0
  • P粉596161915

    P粉5961619152023-08-24 09:59:41

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

    查看:此相关问题

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

    回复
    0
  • 取消回复