我查看了 Rhino源代码以找出它们使用的伪随机函数。显然,它们回退到Java 标准库Math.random
中定义的函数。
的文档Math.random
说:
返回一个带正号的双精度值,大于或等于 0.0 且小于 1.0。返回值是伪随机选择的,从该范围(近似)均匀分布。
当这个方法第一次被调用时,它会创建一个新的伪随机数生成器,就像通过表达式一样
new java.util.Random
这个新的伪随机数生成器随后用于对该方法的所有调用,并且不会在其他任何地方使用。
此方法已正确同步,以允许多个线程正确使用。但是,如果许多线程需要以很高的速率生成伪随机数,则可能会减少每个线程对拥有自己的伪随机数生成器的争用。
所以我检查了文档java.util.Random
并找到了这个(对于默认构造函数):
创建一个新的随机数生成器。它的种子根据当前时间初始化为一个值:
public Random() { this(System.currentTimeMillis()); }
在同一毫秒内创建的两个 Random 对象将具有相同的随机数序列。
所以现在我们确定种子是当前时间(以毫秒为单位)。此外,第二个构造函数的文档说:
使用单个长种子创建一个新的随机数生成器:
public Random(long seed) { setSeed(seed); }
由方法 next 用来保存伪随机数生成器的状态。
该方法的文档setSeed
说:
使用单个长种子设置此随机数生成器的种子。setSeed 的一般约定是,它会更改此随机数生成器对象的状态,使其处于与刚刚使用参数种子作为种子创建时完全相同的状态。方法 setSeed 由 Random 类实现,如下所示:
synchronized public void setSeed(long seed) {
this.seed = (seed ^ 0x5DEECE66DL) & ((1L << 48) - 1);
haveNextNextGaussian = false;
}
由 Random 类实现的 setSeed 恰好只使用给定种子的 48 位。然而,一般来说,覆盖方法可以使用长参数的所有 64 位作为种子值。注意:虽然种子值是一个 AtomicLong,但这个方法仍然必须同步,以确保 haveNextNextGaussian 的语义正确。
用于生成随机数的实际方法nextDouble
是:
从该随机数生成器的序列中返回 0.0 到 1.0 之间的下一个伪随机、均匀分布的双精度值。
该nextDouble
函数的实现如下:
public double nextDouble() {
return (((long)next(26) << 27) + next(27))
/ (double)(1L << 53);
}
显然它取决于功能next
:
生成下一个伪随机数。子类应该覆盖它,因为它被所有其他方法使用。
该next
函数的实现如下:
synchronized protected int next(int bits) {
seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
return (int)(seed >>> (48 - bits));
}
这就是您正在寻找的伪随机函数。正如文档中所说:
这是一个线性同余伪随机数生成器,由 DH Lehmer 定义并由 Donald E. Knuth 在计算机编程艺术,第 2 卷:半数值算法,第 3.2.1 节中描述。
但是请注意,这只是 Rhino 使用的随机数生成器。Spidermonkey 和 V8 等其他实现可能有自己的伪随机数生成器。