P粉4821083102023-08-24 22:35:05
不,不可能播种 Math.random()
。 ECMAScript 规范在这个主题上故意含糊不清,没有提供播种方法,也没有要求浏览器甚至使用相同的算法。所以这样的功能必须由外部提供,幸运的是这并不太困难。
我用纯 JavaScript 实现了许多优秀、简短且快速的伪随机数生成器 (PRNG) 函数。所有这些都可以播种并提供高质量的数字。这些并非出于安全目的 - 如果您需要可种子的 CSPRNG,请查看ISAAC。 p>
首先,请注意正确初始化您的 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 是 PractRand 随机数测试套件的一部分(它当然通过)。 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 解释器处理得更快,但任何乘法或加法都会导致它切换回浮点数,从而导致性能下降。
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 并且不需要数十亿个随机数,我会推荐这个(请参阅生日问题)。
截至 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)。实际上,当使用浮点数时(例如在这些实现中),它不应该导致问题,但如果依赖原始最低阶位,则可能会导致问题。
这是 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; } }
P粉5961619152023-08-24 09:59:41
不,不可能播种 Math.random()
,但编写自己的生成器相当容易,或者更好的是,使用现有的生成器。
查看:此相关问题。
此外,请参阅 David Bau 的博客,了解有关播种的更多信息.