在 Javascript 中播种随机数生成器

IT技术 javascript random random-seed seeding
2021-01-12 20:33:00

是否可以Math.random在 JavaScript 中设置随机数生成器 ( ) 的种子

6个回答

我已经在纯 JavaScript 中实现了许多优秀、简短且快速的伪随机数生成器(PRNG) 函数。所有这些都可以播种并提供高质量的数字。

首先,注意正确初始化您的 PRNG。为了简单起见,下面的生成器没有内置的种子生成程序,而是接受一个或多个 32 位值作为PRNG的初始种子状态相似或稀疏的种子(例如 1 和 2 的简单种子)具有低熵,并且会导致相关性或其他质量问题,导致输出具有相似的属性(例如随机生成的级别相似)。为避免这种情况,最佳做法是使用分布良好的高熵种子初始化 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;
    }
}

返回函数每次后续调用都会xmur3产生一个新的 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();

然而,这只是一种可能的解决方案。或者,只需选择一些虚拟数据来填充种子,然后将生成器推进几次(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;
    }
}

桑树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 和 BlackmanXorshift 家族的新成员(Vigna 教授还负责 Xorshift128+ 算法,支持大多数底层Math.random实现)。它是最快的生成器,可提供 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(jenkins的小快)

这是 JSF 或 Bob Jenkins (2007) 的“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;
    }
}
@Lachmanski 是真的,但那些受 32 位(和 Park-Miller 31 位)的约束。UsingMath.imul允许它溢出,就像在 C 中对 32 位整数使用乘法时一样。你所建议的是一个 LCG,它利用了整个 JS 的整数空间,这绝对是一个值得探索的有趣领域。:)
2021-03-15 20:33:00
我相信您从 Pierre L'ecuyer 的“线性同余生成器表...”中引用的值可能会超过 Javascript 中的最大整数大小。(2^32-1) * 741103597 ≈ 3e18 的最大种子,大于 JavaScript 的最大 int 大小 ≈ 9e15。我认为 Pierre 书中的以下值在本地范围内具有最大的周期:seed = (seed * 185852 + 1) % 34359738337.
2021-03-20 20:33:00
@blobber2 不确定你的意思,但原始代码来自这里(与其他人一起):github.com/bryc/code/blob/master/jshash/PRNGs.md或多或少是存储库中的要点:-)
2021-03-20 20:33:00
这太棒了!我可以将您的 sfc32 复制到 LGPL 程序中吗?
2021-04-03 20:33:00
当然,可以随意使用代码用于任何目的:)
2021-04-04 20:33:00

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

查看:这个相关问题

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

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

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

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

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

在我看来,这个解决方案的优雅来自于没有任何“神奇”数字(除了 10000,它代表了你必须扔掉的最少数字以避免奇怪的模式 - 请参阅值为101001000 的结果)。简洁也不错。

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

-1,这根本不是一个统一的采样器 - 它非常偏向 0 和 1(参见jsfiddle.net/bhrLT/17,这可能需要一段时间来计算)。连续的值是相关的——每 355 个值,甚至每 710 个,都是相关的。请使用更深思熟虑的东西!
2021-03-21 20:33:00
有没有办法证明这个 RNG 生成均匀分布的数字?实验上它似乎是:jsfiddle.net/bhrLT
2021-03-26 20:33:00
问题不在于创建一个加密安全的随机数生成器,而是在 javascript 中工作的东西,对快速演示有用等。我将采取一些快速而简单的方法,为此目的提供超过一百万个随机数的漂亮分布。
2021-03-28 20:33:00
6,000,000 ops/second相当快,我不打算每次点击产生超过 ~3,000,000。开玩笑,这太棒了。
2021-04-08 20:33:00
当心。Math.sin() 可以在客户端和服务器上给出不同的结果。我使用 Meteor(在客户端和服务器上使用 javascript)。
2021-04-09 20:33:00

不,但这是一个简单的伪随机生成器,这是我从维基百科改编乘法进位的实现(此后已被删除):

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;
}
@Michael_Scharf 1) 种子变化m_w,而不是m_z. 2)m_wm_z都基于它们以前的值而改变,因此它确实修改了结果。
2021-03-14 20:33:00
当我将它与我的随机颜色生成器 (HSL) 一起使用时,它只生成绿色和青色。原始随机生成器生成所有颜色。因此,它不相同或不起作用。
2021-03-16 20:33:00
有没有人测试过这个函数的随机性?
2021-03-30 20:33:00
当我使用此代码时,我没有得到均匀分布的结果。无论种子如何,输出序列都非常相似。这使得它对我的游戏没有帮助。
2021-04-02 20:33:00
这是具有相当长周期乘以进位 (MWC)随机发生器。改编自维基百科随机数生成器
2021-04-03 20:33:00

Antti Sykäri 的算法很好也很简短。我最初做了一个变体,Math.random当你调用时替换 JavaScript 的Math.seed(s),但后来 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 没有的另一个功能:多个独立的随机生成器。如果您希望同时运行多个可重复的模拟,这一点尤其重要。

请不要使用这个。请花时间改用名为的局部变量random而不是覆盖本机 javascript 函数。覆盖Math.random可能会导致 JIST 编译器未优化您的所有代码。
2021-03-11 20:33:00
如果您返回该函数而不是Math.random允许您拥有多个独立生成器的设置,对吗?
2021-03-16 20:33:00
每次你做的时间Math.seed(42);会重置功能,所以如果你做var random = Math.seed(42); random(); random();0.70...的话0.38...如果您通过var random = Math.seed(42);再次拨打电话来重置它,那么您下次拨打电话时random()您会0.70...再次收到,下次您还会收到0.38...
2021-03-24 20:33:00
如果这对您很重要,请务必查看上面关于随机性分布的评论:stackoverflow.com/questions/521295/...
2021-03-27 20:33:00
如何重复由此产生的随机数?它每次都不断给出新的数字
2021-03-30 20:33:00