【问题标题】:What's with 181783497276652981 and 8682522807148012 in Random (Java 7)?随机(Java 7)中的 181783497276652981 和 8682522807148012 是什么?
【发布时间】:2013-08-08 04:00:24
【问题描述】:

为什么在Random.java 中选择了1817834972766529818682522807148012

以下是 Java SE JDK 1.7 的相关源代码:

/**
 * Creates a new random number generator. This constructor sets
 * the seed of the random number generator to a value very likely
 * to be distinct from any other invocation of this constructor.
 */
public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}

private static long seedUniquifier() {
    // L'Ecuyer, "Tables of Linear Congruential Generators of
    // Different Sizes and Good Lattice Structure", 1999
    for (;;) {
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L;
        if (seedUniquifier.compareAndSet(current, next))
            return next;
    }
}

private static final AtomicLong seedUniquifier
    = new AtomicLong(8682522807148012L);

因此,在没有任何种子参数的情况下调用new Random() 会获取当前的“种子唯一性”并与System.nanoTime() 进行异或。然后它使用181783497276652981 创建另一个种子唯一标识符,以便在下次调用new Random() 时存储。

文字181783497276652981L8682522807148012L 没有放在常量中,但它们不会出现在其他任何地方。

起初,评论给了我一个简单的线索。在线搜索该文章会产生the actual article8682522807148012 没有出现在论文中,但181783497276652981 确实出现了——作为另一个数字1181783497276652981 的子字符串,它是181783497276652981,前面带有1

论文声称1181783497276652981 是一个可以为线性同余生成器产生良好“优点”的数字。这个数字是否只是错误地复制到 Java 中? 181783497276652981 有可接受的优点吗?

为什么选择8682522807148012

在网上搜索这两个数字都没有任何解释,只有this page 还注意到1181783497276652981 前面被丢弃。

是否可以选择与这两个数字一样有效的其他数字?为什么或为什么不?

【问题讨论】:

  • 我只想指出,没有提到的常量(即使是开头的较大的常量)太大而无法容纳,尽管乘法肯定会导致溢出。跨度>
  • 8682522807148012 是该类先前版本的遗留物,如in the revisions made in 2010 所示。 181783497276652981L 似乎确实是一个错字,您可以提交错误报告。
  • 要么是错字,即错误,要么是具有未公开动机的功能。你得问问作者。你在这里得到的任何东西都或多或少是不知情的意见。如果您认为这是一个错误,请提交错误报告。
  • 特别是考虑到不同的答案,这可能是每个常数的两个独立问题。
  • 很遗憾看到这样一个基础类中内置了全局可伸缩性瓶颈。 seedUniquifier 在 64 核机箱上会变得非常有竞争力。线程本地会更具可扩展性。

标签: java random


【解决方案1】:
  1. 这个数字是否只是错误地复制到了 Java 中?

    是的,似乎是一个错字。

  2. 181783497276652981 有可接受的优点吗?

    这可以使用论文中介绍的评估算法来确定。但是“原始”数字的优点可能更高。

  3. 为什么选择 8682522807148012?

    似乎是随机的。可能是编写代码时 System.nanoTime() 的结果。

  4. 是否可以选择与这两个数字一样有效的其他数字?

    并非每个数字都同样“好”。所以,没有。

播种策略

JRE 的不同版本和实现之间的默认种子模式存在差异。

public Random() { this(System.currentTimeMillis()); }
public Random() { this(++seedUniquifier + System.nanoTime()); }
public Random() { this(seedUniquifier() ^ System.nanoTime()); }

如果您连续创建多个 RNG,则第一个是不可接受的。如果它们的创建时间落在相同的毫秒范围内,它们将给出完全相同的序列。 (相同的种子 => 相同的序列)

第二个不是线程安全的。多个线程在同时初始化时可以获得相同的 RNG。此外,后续初始化的种子往往是相关的。根据系统的实际计时器分辨率,种子序列可以线性增加(n,n+1,n+2,...)。正如How different do random seeds need to be? 和参考论文Common defects in initialization of pseudorandom number generators 中所述,相关种子可以在多个RNG 的实际序列之间产生相关性。

第三种方法创建随机分布的不相关种子,甚至跨线程和后续初始化。 所以当前的 java 文档:

此构造函数将随机数生成器的种子设置为 value 很可能与 this 的任何其他调用不同 构造函数。

可以通过“跨线程”和“不相关”进行扩展

种子序列质量

但播种序列的随机性仅与底层 RNG 一样好。 此 java 实现中用于种子序列的 RNG 使用 c=0 和 m=2^64 的乘法线性同余生成器 (MLCG)。 (模数 2^64 由 64 位长整数的溢出隐式给出) 由于零 c 和模数 2 的幂,“质量”(周期长度、比特相关性……)是有限的。正如论文所说,除了总周期长度外,每个位都有自己的周期长度,对于不太重要的位,周期长度会呈指数下降。因此,较低位具有较小的重复模式。 (seedUniquifier() 的结果应该是位反转的,在实际 RNG 中被截断为 48 位之前)

但它很快!并且为了避免不必要的比较和设置循环,循环体应该很快。这可能解释了这种特定 MLCG 的用法,无需加法,无需异或,只需一次乘法。

并且上述论文提供了 c=0 和 m=2^64 的良好“乘数”列表,如 1181783497276652981。

总而言之:努力@JRE-developers ;) 但是有一个错字。 (但谁知道呢,除非有人评估,否则有可能缺失的前导1实际上提高了种子RNG。)

但有些乘数肯定更糟: “1”导致一个恒定的序列。 “2”导致单比特移动序列(以某种方式相关) ...

RNG 的序列间相关性实际上与 (Monte Carlo) 模拟相关,其中多个随机序列被实例化甚至并行化。因此,需要一个好的种子策略来获得“独立”的模拟运行。因此,C++11 标准引入了Seed Sequence 的概念,用于生成不相关的种子。

【讨论】:

  • 至少它仍然很奇怪,如果他们丢弃了最不重要的一个而不是最重要的一个,那么每次乘法都会丢失一点,直到最终(在 62 步之后)seedUniquifier 卡在零。
【解决方案2】:

如果您认为用于随机数生成器的方程是:

其中 X(n+1) 是下一个数,a 是倍数,X(n) 是当前数,c 是增量,m 是模数。

如果你进一步查看Random,a、c 和 m 是在类的标题中定义的

private static final long multiplier = 0x5DEECE66DL;   //= 25214903917 -- 'a'
private static final long addend = 0xBL;               //= 11          -- 'c'
private static final long mask = (1L << 48) - 1;       //= 2 ^ 48 - 1  -- 'm'

看看protected int next(int bits)的方法,这就是方程式的实现

nextseed = (oldseed * multiplier + addend) & mask;
//X(n+1) =  (X(n)   *      a     +    c  ) mod m

这意味着seedUniquifier() 方法实际上正在获取 X(n),或者在第一种情况下,在初始化 X(0) 时实际上是 8682522807148012 * 181783497276652981,然后通过 System.nanoTime() 的值进一步修改该值。该算法与上面的等式一致,但具有以下 X(0) = 8682522807148012, a = 181783497276652981, m = 2 ^ 64 和 c = 0。但是由于 mod m 是由长溢出执行的上面的等式就变成了

查看the paper,a = 1181783497276652981 的值是 m = 2 ^ 64,c = 0。所以它似乎只是一个错字,而出现的 X(0) 的值 8682522807148012Random 的遗留代码中看似随机选择的数字。 As seen here. 但是这些选择的数字的优点仍然有效,但正如 Thomas B 所提到的那样。可能不如论文中的“好”。

编辑 - 以下原始想法已被澄清,因此可以忽略,但留作参考

这让我得出结论:

  1. 本文引用的不是数值本身,而是由于a、c、m的取值不同,取值的方法

  2. 除前导 1 之外的其他值相同且注释放错位置(尽管仍然难以相信这一点),这纯属巧合

对论文中的表格存在严重误解,开发人员只是随机选择了一个值,因为当它乘以首先使用表格值的意义时,尤其是你可以只需以任何方式提供您自己的种子值,在这种情况下甚至不考虑这些值

所以回答你的问题

是否可以选择与这两个数字一样有效的其他数字?为什么或为什么不?

是的,可以使用任何数字,事实上,如果您在实例化 Random 时指定种子值,则您正在使用任何其他值。该值对生成器的性能没有任何影响,这是由类中硬编码的 a、c 和 m 的值决定的。

【讨论】:

  • 不是真的 - 有两种算法: (i) 1 每次调用构造函数时创建一个新的随机种子。该算法使用简单的 X_n+1=X_n * a。由于长溢出,这相当于 X_n+1=X_n * a mod m。 a = 181783497276652981 和 m = 2^64。 (ii) 另一种算法,从给定的种子开始,产生一系列随机数。第二个算法是您提到的那个,文档解释说“这是一个线性同余伪随机数生成器,如 Knuth 在计算机编程艺术中所述”。
  • @assylias 我明白你的意思,被Random 的源代码和引用的论文所吸引,我已经完全超过了原来的问题,很快就会编辑,谢谢。
【解决方案3】:

根据您提供的链接,他们选择了(在添加缺少的 1 后 :))2^64 的最佳产量,因为 long 不能有来自 2^128 的数字

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-08
    • 2014-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多