问题

网上常能见到的一段 JS 随机数生成算法如下,为什么用 9301, 49297, 233280 这三个数字做基数?

回答
网上流传的这段 JavaScript 随机数生成算法,通常是这样的形式:

```javascript
function random() {
var seed = 12345; // 初始种子,这里的数字是任意的,但通常会固定
seed = (seed 9301 + 49297) % 233280;
return seed / 233280;
}
```

之所以选择 9301、49297 和 233280 这三个数字作为“基数”(更准确地说,它们是乘数、加数和模数),这背后是有一定的数论和计算机科学的考量的。这是一种非常经典的线性同余生成器 (Linear Congruential Generator, LCG) 的实现。

我们来拆解一下为什么选择这些数字,并尽量用通俗易懂的方式解释清楚:

什么是线性同余生成器 (LCG)?

LCG 的核心思想是:下一个随机数是由上一个随机数通过一个固定的数学公式计算出来的。 这个公式长这样:

$X_{n+1} = (a cdot X_n + c) pmod m$

$X_n$:当前的随机数(或者说“状态”)。
$X_{n+1}$:下一个随机数。
$a$:乘数 (multiplier)。
$c$:增量 (increment)。
$m$:模数 (modulus)。
$pmod m$:取模运算,意思是计算除以 $m$ 后的余数。

在你的例子中:

$a = 9301$
$c = 49297$
$m = 233280$

而 `seed` 就是我们的 $X_n$,每次调用 `random()` 函数时,`seed` 都会更新成 $X_{n+1}$。最后,`seed / 233280` 是为了将生成的整数范围(0 到 233279)映射到 [0, 1) 的浮点数范围,这是 JavaScript 中 `Math.random()` 的常见返回值。

为什么选择这三个特定的数字?

选择 $a, c, m$ 这三个参数不是随便来的,它们需要满足一些条件,才能生成出“质量”相对不错的伪随机数序列。这里的“质量”指的是:

1. 周期长 (Long Period):生成的随机数序列在重复之前应该尽可能长。如果周期太短,很快就会出现重复的模式,就不“随机”了。
2. 分布均匀 (Good Distribution):生成的随机数在期望的范围内应该尽可能均匀地分布,没有明显的聚集或空白。
3. 统计特性良好 (Good Statistical Properties):当分析一系列生成的随机数时,它们应该表现出随机性应有的统计特征,比如独立性、缺乏相关性等。

9301, 49297, 233280 这三个数字被选用的原因,很大程度上是因为它们是某些经典 LCG 算法的参数,这些参数经过了研究和实践的检验,能够生成一个相对较长的周期和还不错的统计特性。

具体来说,这些参数的选取,尤其是模数 $m$,往往与计算机的字长、硬件平台的特性或者特定的数论性质有关。

模数 $m = 233280$:
这个数字本身没有特别突出的质数特性,但它是一个相对较大的整数。
选择一个较大的模数通常有助于延长伪随机数序列的周期。
它也被认为是工程上为了平衡计算效率和随机数质量而选取的一个值。

乘数 $a = 9301$:
乘数 $a$ 的选择对周期长度和统计分布有重要影响。
某些理论表明,为了获得最大周期(即 $m$),乘数 $a$ 需要满足一些特定条件,比如与模数 $m$ 互质,以及满足一些更复杂的数论条件。
9301 是一个素数,素数乘数通常被认为能提供更好的随机性。

增量 $c = 49297$:
增量 $c$ 的选择会影响序列的平移和跳跃,对于避免死循环和改善分布也很重要。
当 $c eq 0$ 时,LCG 的周期可以达到 $m$。如果 $c = 0$,则称为乘法同余生成器,其周期可能远小于 $m$。

为什么不选择其他数字?

想象一下,如果我们选择的数字很糟糕,会发生什么?

如果 $a, c, m$ 都很小:比如 $m=10$,$a=2$,$c=1$。如果我们以 `seed=0` 开始:
$X_1 = (2 cdot 0 + 1) pmod{10} = 1$
$X_2 = (2 cdot 1 + 1) pmod{10} = 3$
$X_3 = (2 cdot 3 + 1) pmod{10} = 7$
$X_4 = (2 cdot 7 + 1) pmod{10} = 15 pmod{10} = 5$
$X_5 = (2 cdot 5 + 1) pmod{10} = 11 pmod{10} = 1$
接下来就是 3, 7, 5, 1... 周期只有 4。很快就重复了,而且只用到了 1, 3, 5, 7 这几个数,分布非常不均匀。

如果 $a$ 和 $m$ 有公因数:这会限制可能生成的随机数的范围。

历史和演变

LCG 是一种非常古老且相对容易实现的伪随机数生成器,在计算机科学的早期就得到了广泛应用。你看到的这段代码,很可能是在某个特定的应用场景下,为了快速、简便地生成一个“够用”的随机数而采用的经典组合。

需要强调的是,这段 LCG 算法虽然经典,但它并不是现代密码学意义上的“强随机数生成器”。 对于需要高安全性、高统计精度的场景(比如加密、科学模拟、金融建模等),通常会使用更复杂、更安全的算法,比如 Mersenne Twister (梅森旋转算法)、Xorshift 算法,或者基于硬件真随机数源的生成器。

所以,选择 9301, 49297, 233280,是基于这些数字在 LCG 理论中能够提供一个相对不错的周期和分布,是一种工程上的权衡和历史传承。 它们并非有什么“神秘”的数学含义,而是经过实践检验的参数组合,能够在简单实现和一定随机性质量之间找到一个平衡点。

现在,如果你在一个地方看到这段代码,很可能是在:

老旧的代码库:项目年代比较久远,当时这是比较主流的实现方式。
简单的演示或教程:用一个易于理解的例子来讲解伪随机数的概念。
对随机性要求不高的场景:例如游戏中的非关键元素生成、简单的可视化效果等。

理解这些参数的意义,有助于我们认识到伪随机数生成的原理,以及不同算法之间的差异和适用场景。

网友意见

user avatar

很多人认为这是简单的Magic Number,其实这背后有内在的原因,这三个数字并不是随便乱选出来的。

入门级的选择标准

这种伪随机数生成器叫做线性同余生成器(LCG, Linear Congruential Generator),几乎所有的运行库提供的rand都是采用的LCG,形如:

生成的伪随机数序列最大周期m,范围在0到m-1之间。要达到这个最大周期,必须满足

  • c与m互质
  • a - 1可以被m的所有质因数整除
  • 如果m是4的倍数,a - 1也必须是4的倍数

以上三条被称为Hull-Dobell定理。

作为一个伪随机数生成器,周期不够大是不好意思混的,所以这是要求之一。

可以看到,a=9301, c = 49297, m = 233280这组参数,以上三条全部满足。

进阶级的选择标准

要在伪随机数生成器界混,仅仅入门是不够的。

从工程的角度来讲,的值要(在合理的范围内)足够小,以避免溢出的问题。

从安全(实用)性的角度来讲,还要满足良好的随机性,这一点可以通过Knuth's Spectral Test来评估(见[2][3]),要通过2,3,4,5以及6维的Spectral Test才行。Spectral Test考察的就是生成的伪随机数序列在超空间的网格结构(lattice structure),当年IBM的RANDU子程序闹出的乌龙,连3维的Spectral Test就不能通过,上图嘲讽下:

其中每个点代表三个连续的RANDU生成的伪随机数值,可以看到所有伪随机数分布在了15个二维平面上。

在这种要求面前,c的值最好:

  • 是质数 (c = 49297就是质数)
  • 接近,(m = 233280时为49297.86460172205)

所以有了这样一些基本的标准,能够选择的参数范围就小了很多,弄个程序跑下Spectral Test,就能得到可选的参数组。

如果想要更加详尽的了解LCG伪随机数生成器的性质以及参数选取、测试的数学理论,可以尝试阅读《计算机程序设计艺术》卷2第3章。

参考资料:

[1]

nuclear.fis.ucm.es/COMP

[2]

random.mat.sbg.ac.at/te

[3] Knuth, Donald E. (1981), The Art of Computer Programming volume 2: Seminumerical algorithms (2nd ed.), Addison-Wesley, p. 89.

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有