【问题标题】:Predict the Seed of Javascript's Math.random预测 Javascript 的 Math.random 的种子
【发布时间】:2012-10-29 09:43:07
【问题描述】:

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

我听说 Math.random 的种子是通过当前时间生成的,对吗?他们必须一直使用当前时间,精确到毫秒或其他时间,因为如果你不这样做,你会得到相同的结果。

种子到底是什么?是像“10:45”这样的时间还是像“10:45 11/8/12”这样的时间和日期,还是某种组合?

如何找到种子,以便预测输出?

我希望能够插入这个:

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

进入我的网址栏,并能够预测结果。这可能吗?

【问题讨论】:

  • Math.random() 的全部意义在于你无法预测它(至少,并非没有很大困难)。
  • 有没有人在这里阅读v8 的源代码给我们一个明确的答案?
  • @TiansHUo - 我阅读了Rhino的源代码并根据我阅读的内容回答了问题:stackoverflow.com/a/13303029/783743

标签: javascript random seed


【解决方案1】:

我查看了 Rhino source code 以找出它们使用的伪随机函数。显然他们fall back 到Math.random 定义在Java standard library 中的函数。

Math.random 的文档说:

返回一个带正号的双精度值,大于或等于 0.0 且小于 1.0。返回值是伪随机选择的,从该范围(近似)均匀分布。

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

new java.util.Random

这个新的伪随机数生成器随后用于对该方法的所有调用,并且不会在其他任何地方使用。

此方法已正确同步,以允许多个线程正确使用。但是,如果许多线程需要以很高的速度生成伪随机数,则可能会减少每个线程对拥有自己的伪随机数生成器的争用。

所以我检查了java.util.Random 的文档,发现this(对于默认构造函数):

创建一个新的随机数生成器。它的种子根据当前时间初始化为一个值:

public Random() { this(System.currentTimeMillis()); }

在同一毫秒内创建的两个 Random 对象将具有相同的随机数序列。

所以现在我们确定种子是当前时间(以毫秒为单位)。此外,second constructor 的文档说:

使用单个长种子创建一个新的随机数生成器:

public Random(long seed) { setSeed(seed); }

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

setSeed 方法的 documentation 表示:

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

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

类 Random 的 setSeed 实现碰巧只使用给定种子的 48 位。然而,一般来说,覆盖方法可以使用长参数的所有 64 位作为种子值。注意:虽然种子值是一个 AtomicLong,但这个方法仍然必须同步,以保证 haveNextNextGaussian 的语义正确。

用于生成随机数的actual method是nextDouble:

从这个随机数生成器的序列中返回下一个伪随机、均匀分布的双精度值,介于 0.0 和 1.0 之间。

nextDouble函数的实现如下:

public double nextDouble() {
    return (((long)next(26) << 27) + next(27))
        / (double)(1L << 53);
}

显然depends 上的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 在计算机编程艺术,第 2 卷:半数值算法,第 3.2.1 节中描述。​​

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

【讨论】:

  • +1,很好的答案,所以这意味着至少我可以根据时间的准确性枚举一个可能的时间范围,至少我可以得到一个可能的随机数列表(这是出于安全原因绝对不安全)。但对于最初的问题,可能不会,除非你能准确地得到毫秒。
  • @Aadit 你说得对,这取决于实现,如果你使用 javascript 编写 webapp,不同的 web 浏览器使用不同的伪随机数生成器。对于特定的应用程序,这可能会造成严重破坏。众所周知,线性全余方法不具备所有所需的“随机”属性——强序列相关性。见stackoverflow.com/questions/19507469/…。顺便说一句,答案很好。
【解决方案2】:

不,您无法预测种子,但您可以预先生成足够的数字,以便准确地强制匹配。

无论如何,首先阅读 RNG 上的 wiki 页面 - http://en.wikipedia.org/wiki/Random_number_generation,看看 PRNG 的实际实现。

【讨论】:

    【解决方案3】:

    种子可能比毫秒计数更多,因为您可以在同一毫秒内多次调用 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,这样您就不会两次获得相同的种子值。

    这是预测Math.random() 输出的 100% 准确方法:

    Math.random = function () { return .5; };
    

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

    【讨论】:

    • @43.52.4D。有一种方法可以找出答案。开始做吧,十年后我们会有答案,意思是,如果可能的话……你是真的吗?!
    • 这是 Chrome 的 Math.random() 背后的代码:code.google.com/p/v8/source/browse/trunk/src/v8.cc#170 看起来它基本上将使用您系统的随机数生成器,所以它的好坏最终取决于您的操作系统的 RNG。
    【解决方案4】:

    种子是一个数值,所以我猜如果你调用Date.now()(或旧版浏览器的new Date().getTime()),你会得到它。

    但是,我不确定何时获取该种子,或者该种子是与当前页面隔离的,还是对整个浏览器进程通用的。预测随机数应该是非常困难或不可能的,这就是随机数的全部意义所在。

    【讨论】:

      猜你喜欢
      • 2013-05-22
      • 1970-01-01
      • 1970-01-01
      • 2011-07-31
      • 1970-01-01
      • 2021-02-26
      • 2023-04-11
      • 2010-11-06
      • 2017-05-03
      相关资源
      最近更新 更多