随机(Java 7)中的181783497276652981和8682522807148012是什么?


112

为什么1817834972766529818682522807148012在选择Random.java

以下是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不会放置在常量中,但是它们不会出现在其他任何地方。

起初,评论给了我一个轻松的线索。在线搜索该文章会产生实际的文章8682522807148012没有出现在纸上,但181783497276652981确实出现-作为另一个号码,一个子1181783497276652981,这是181783497276652981一个1前缀。

该论文声称,1181783497276652981这个数字对于线性同余生成器具有良好的“优点”。这个数字是否只是简单地复制到Java中?是否181783497276652981有一个可以接受的优点?

为什么8682522807148012选择了?

在线搜索任何一个数字都不会产生任何解释,只有该页面还注意到1前面的掉落181783497276652981

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


我只想指出,尽管乘法肯定会导致溢出,但所提到的所有常量(即使是较大的常量(以开头的常量开头)也不会太大)。
nanofarad 2013年

6
8682522807148012从2010年的修订版可以看出该类是该类先前版本的遗产。在181783497276652981L似乎是一个错字的确,你可以发送错误报告。
assylias 2013年

6
这可能是拼写错误(即错误),也可能是动机不明的功能。您将不得不问作者。您到达这里的任何事情都会或多或少地无知。如果您认为这是一个错误,请提交错误报告。
洛恩侯爵,

1
特别是在给出不同答案的情况下,每个常数可能是两个独立的问题。
马克·赫德

1
可悲的建成这样一个根本的类别全球可扩展性的瓶颈。seedUniquifier可以在64核盒子上变得非常有竞争力。本地线程本来可以更扩展。
usr

Answers:


57
  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,...)。如《随机种子需要有多少不同?以及参考论文伪随机数生成器初始化中的常见缺陷,相关种子可以在多个RNG的实际序列之间产生相关性。

第三种方法即使在线程和后续初始化之间,也会创建随机分布的种子,因此会产生不相关的种子。因此,当前的Java文档:

该构造函数将随机数生成器的种子设置为一个很有可能与该构造函数的任何其他调用不同的值。

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

种子序列质量

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

但是速度很快!并且为了避免不必要的比较和设置循环,循环主体应该是快速的。这可能解释了此特定MLCG的用法,而没有加法,没有异或,只是一个乘法。

上面提到的论文给出了c = 0和m = 2 ^ 64的良好“乘数”列表,如1181783497276652981。

总而言之:A努力@ JRE-developers;)但是有一个错字。(但是谁知道,除非有人对其进行评估,否则丢失的前导1实际上可能会改善播种RNG。)

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

RNG的序列间相关实际上与(Monte Carlo)模拟有关,在该模拟中,多个随机序列被实例化甚至并行化。因此,良好的播种策略对于获得“独立”仿真运行是必要的。因此,C ++ 11标准引入了用于生成不相关种子的种子序列的概念。


3
至少这仍然很奇怪,如果他们放弃了最低有效位而不是最高有效位,那么每个乘法都会丢失一点,直到最终(在62步之后)seedUniquifier变为零。
哈罗德

9

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

LCGE方程

其中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一致。

eq2

纸上看,a =的值1181783497276652981是m = 2 ^ 64,c =0。所以它似乎只是一个错字,8682522807148012而X(0)的值似乎是一个从旧有代码中随机选择的数字为Random如此处所示。但是这些选择的数字的优点仍然是有效的,但正如托马斯·B。(Thomas B.)所提到的那样,可能不如论文中的“好”。

编辑-以下原始思想已经澄清,可以忽略不计,但留作参考

这使我得出结论:

  1. 本文的参考不是针对值本身,而是针对由于a,c和m的不同值而用于获取值的方法

  2. 只是巧合的是,该值与前导1相同,并且注释放错了位置(尽管仍然难以置信)

要么

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

所以回答你的问题

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

是的,可以使用任何数字,实际上,如果在实例化“随机”时指定了种子值,那么您将使用其他任何值。该值对生成器的性能没有任何影响,这由在类内进行硬编码的a,c和m的值确定。


1
并不是真的-有两种算法:(i)1在每次调用构造函数时创建一个新的随机种子。该算法使用简单的X_n + 1 = X_n * a。由于长时间溢出,这等效于X_n + 1 = X_n * a mod m。其中a = 181783497276652981和m = 2 ^ 64。(ii)从给定的种子开始,产生一系列随机数的另一种算法。第二种算法是您提到的算法,文档解释说:“ 这是线性同余的伪随机数生成器,如Knuth在《计算机编程的艺术》中所述。”
assylias 2013年

1
@assylias我明白您的意思,被源代码深深吸引Random,被引用的论文已经完全超出了原始问题,将很快进行编辑,谢谢。
Java Devil

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.