预测Javascript的Math.random的种子

9

好的,我正在研究如何使用Math.random方法生成随机数。到目前为止,我了解到它从一个“随机”的种子开始,并将该种子插入到某个复杂的方程中以创建一个随机数。如果种子始终相同,结果是否总是相同?

我听说Math.random的种子是通过当前时间生成的,这是正确的吗?他们必须一直使用当前时间到毫秒级别,否则你会得到相同的结果。

种子到底是什么?是时间,比如“10:45”,还是包括日期和时间,比如“10:45 11/8/12”或某些组合?

我该如何找到种子,以便预测输出结果?

我希望能够插入以下内容:

alert(Math.floor((Math.random()*10)+1));

在我的URL地址栏中输入内容,然后能够预测结果。这是否可能?

Math.random() 的整个意义在于你无法预测它(至少不是轻易能做到的)。 - Niet the Dark Absol
1
这里有没有人阅读v8源代码,能否给我们一个确定的答案? - TiansHUo
@TiansHUo - 我阅读了Rhino的源代码,并根据我所读的内容回答了这个问题:https://dev59.com/52rXa4cB1Zd3GeqPBKOu#13303029 - Aadit M Shah
4个回答

16

我查阅了Rhino 源代码,找到了他们使用的伪随机函数。显然,他们会 回退 到在 Java 标准库 中定义的Math.random 函数。

Math.random 的文档介绍如下:

返回一个正号、大于等于 0.0 且小于 1.0 的 double 值。返回值在该范围内以伪随机方式(大致)均匀分布。

当第一次调用此方法时,它会像表达式一样创建一个新的伪随机数生成器:

new java.util.Random

此后,所有调用此方法的操作将使用这个新的伪随机数生成器,并且不会在其他地方使用它。

为了正确地被多个线程使用,此方法已经被适当同步。然而,如果有许多线程需要以极大的速率生成伪随机数,则对于每个线程拥有自己的伪随机数生成器可能会减少竞争。

因此,我查阅了java.util.Random的文档并找到了此链接(默认构造函数)。其中写道:

创建一个新的随机数生成器。其种子值基于当前时间进行初始化:

public Random() { this(System.currentTimeMillis()); }
在同一毫秒内创建的两个随机对象将具有相同的随机数序列。现在我们确信种子是以毫秒为单位的当前时间。此外,第二个构造函数 的文档说明如下:创建一个新的随机数生成器,使用单个长种子。
public Random(long seed) { setSeed(seed); }

用于 next 方法来保存伪随机数生成器的状态。

setSeed 方法的文档如下所述:

使用单个 long 种子设置此伪随机数生成器的种子。setSeed 方法的一般约定是,它会改变此随机数生成器对象的状态,使其状态与使用参数 seed 作为种子刚创建时完全相同。setSeed 方法由 Random 类实现,具体如下:

synchronized public void setSeed(long seed) {
    this.seed = (seed ^ 0x5DEECE66DL) & ((1L << 48) - 1);
    haveNextNextGaussian = false;
}

Random类中的setSeed方法仅使用给定种子的48位。然而,通常情况下,重写的方法可能会使用long类型参数的所有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));
}

这就是您正在寻找的伪随机函数。如文档中所述:

这是一个线性同余伪随机数生成器,由D.H. Lehmer定义,并由Donald E. Knuth在计算机程序设计艺术第二卷:半数值算法3.2.1节中描述。

请注意,这只是Rhino使用的随机数生成器。其他实现如Spidermonkey和V8可能有自己的伪随机数生成器。


+1,非常好的答案。这意味着我至少可以根据时间的准确性枚举一系列可能的时间,并且我至少可以获得可能的随机数列表(出于安全原因肯定不安全)。但对于原始问题,除非您能够准确地获取毫秒级别的时间,否则可能无法实现。 - TiansHUo
@Aadit,你说得对,这取决于实现方式。如果你使用JavaScript编写Web应用程序,则不同的Web浏览器使用不同的伪随机数生成器。对于特定的应用程序,这可能会造成混乱。线性同余方法在具有所有所需的“随机”属性方面非常糟糕 - 具有强大的序列相关性。请参见https://dev59.com/g3jZa4cB1Zd3GeqPbjw4. 顺便说一句,回答得很好。 - WetlabStudent

6

种子与毫秒计数可能并不是唯一的因素,因为您可以在同一毫秒内多次调用Math.random(),每次返回的值都是不同的。

for (var i = 0; i < 3; i++) {
    console.log(Math.random(), (new Date()).getTime());
};

我的输出:
0.0617244818713516 1352433709108
0.8024995378218591 1352433709108
0.2409922298975289 1352433709108

如果我要实现它,我可能会基于毫秒计数生成初始种子,每次调用时加1,以便不会重复获得相同的种子值。
这是一种100%准确预测Math.random()输出的方法:
Math.random = function () { return .5; };

现在,Math.random()将始终返回.5

@43.52.4D。有一种方法可以找出答案。开始做吧,十年后我们就会得到答案,也就是说,它是否可能...你是真的吗?! - gdoron
这里是Chrome的Math.random()背后的代码:http://code.google.com/p/v8/source/browse/trunk/src/v8.cc#170。看起来它基本上会使用你系统的随机数生成器,所以它的好坏最终取决于你操作系统的RNG。 - evan

0

种子是一个数字值,所以我猜它应该是你调用 Date.now()(或者对于旧版浏览器使用 new Date().getTime())得到的结果。

然而,我不确定这个种子是在什么时候被取出的,或者这个种子是针对当前页面隔离的还是整个浏览器进程通用的。预测随机数应该是非常困难甚至不可能的,这就是它们被定义为随机的全部意义。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接