【问题标题】:What's wrong with Android's Random.nextInt(2^N)?Android 的 Random.nextInt(2^N) 有什么问题?
【发布时间】:2014-04-07 17:49:36
【问题描述】:

我通过点击按钮多次执行了以下代码:

int UP = 4;
Log.println(Log.DEBUG, "random", new Random().nextInt(UP) + " " 
                               + new Random().nextInt(UP) + " "
                               + new Random().nextInt(UP) + " "
                               + new Random().nextInt(UP) + " "
                               + new Random().nextInt(UP) + " "
                               + new Random().nextInt(UP) + " "
                               + new Random().nextInt(UP) + " "
                               + new Random().nextInt(UP) + " "
 );

我很惊讶在日志中得到了这个:

04-07 21:26:36.659: D/random(15640): 1 1 1 1 1 1 1 1 
04-07 21:26:37.059: D/random(15640): 2 2 2 2 2 2 2 2 
04-07 21:26:37.429: D/random(15640): 2 2 2 2 2 2 2 2 
04-07 21:26:37.789: D/random(15640): 2 2 2 2 2 2 2 2 
04-07 21:26:38.119: D/random(15640): 1 1 1 1 1 1 1 1 
04-07 21:26:38.429: D/random(15640): 1 1 1 1 1 1 1 1 
04-07 21:26:38.739: D/random(15640): 1 1 1 1 1 1 1 1 
04-07 21:26:39.019: D/random(15640): 2 2 2 2 2 2 2 2 
04-07 21:26:39.319: D/random(15640): 2 2 2 2 2 2 2 1 
04-07 21:26:39.599: D/random(15640): 2 1 1 1 1 1 1 1 

——当然,这些序列绝对是非随机的。

我知道,我应该创建 Random 对象的一个​​实例,并根据需要多次调用它的 nexInt() 但是每次创建新的 Random 实例时,Java 都会更改静态种子数by Random() 构造函数:

public Random() { 
    this(++seedUniquifier + System.nanoTime()); 
}
private static volatile long seedUniquifier = 8682522807148012L;

因此产生的序列应该是随机的,无论我们使用一个对象还是每次都使用新对象。但由于某种原因,它们不是。

我决定继续我的研究,用不同的论点致电nextInt()。而我接下来看到的,更让我吃惊。我注意到每次我选择UP 常数作为2 的幂new Random().nextInt(UP) 的结果绝对是非随机的,但是当我选择不同的数字 时,一切还可以。

向上 = 8:

04-07 21:43:47.169: D/random(15789): 5 4 4 4 4 5 5 5 
04-07 21:43:47.809: D/random(15789): 7 7 7 7 7 7 7 7 
04-07 21:43:48.249: D/random(15789): 6 7 7 7 7 6 6 6 
04-07 21:43:48.619: D/random(15789): 6 6 6 6 6 6 6 6 
04-07 21:43:48.999: D/random(15789): 5 5 5 5 5 5 4 4 
04-07 21:43:49.399: D/random(15789): 4 4 4 4 4 4 4 4

向上 = 32:

04-07 21:45:27.979: D/random(15888): 16 15 15 14 15 15 15 24 
04-07 21:45:28.329: D/random(15888): 23 23 23 24 24 24 24 21 
04-07 21:45:28.549: D/random(15888): 22 20 20 20 20 21 21 21 
04-07 21:45:28.849: D/random(15888): 31 31 31 31 31 31 29 29 
04-07 21:45:29.329: D/random(15888): 27 28 28 28 26 26 26 26 

但是对于 UP = 10,结果看起来是随机的,不是吗? --

04-07 21:47:02.189: D/random(15983): 8 4 1 9 6 6 4 2 
04-07 21:47:02.639: D/random(15983): 7 5 3 0 7 5 2 0 
04-07 21:47:02.999: D/random(15983): 3 9 6 4 1 8 6 3 
04-07 21:47:03.379: D/random(15983): 5 4 1 8 6 3 0 8 
04-07 21:47:03.669: D/random(15983): 5 1 8 6 3 3 1 8 
04-07 21:47:03.989: D/random(15983): 3 3 1 4 1 8 6 3 
04-07 21:47:04.269: D/random(15983): 6 6 3 1 8 5 3 0 

所以我只有一个问题:任何人都知道发生了什么以及为什么?为什么nextInt(2^N) 得到的结果是非随机的,而任何其他参数得到的结果都很好?

===================================

更新。 cmets 中的人说,在桌面上 JVM 结果是随机的,独立于作为 nextInt() 参数传递的数字。那么所描述的行为是否仅适用于 Android?

更新 2。 标记为最适合此问题的答案,尤其是下面的讨论,对所发生的情况给出了非常清楚的解释。

【问题讨论】:

  • "但是每次由 Random() 构造函数创建新的 Random 实例时,Java 都会更改静态种子数" 这似乎与您的数据相矛盾。
  • @LouisWasserman 我添加了一段源代码,表明它确实如此......我不知道为什么当 2^N 放在 nextInt() 上时会发生这种情况......对于其他数字,一切看起来很好。
  • 如果我没记错的话,Random 使用两个种子。一个是所有实例的常量种子,另一个是启动时间的AtomicLong
  • 无法在桌面 JVM 上重现,具有任何 UP 值。
  • @Salauyou 你在什么环境下运行它?我运行了您的代码,并预计 UP 各种值的伪随机分布,包括 4、8、32 等

标签: java android random


【解决方案1】:

来自nextInt() 上的 Java 文档:

public int nextInt(int n) 统一返回一个伪随机数 分布在 0(含)和指定值之间的 int 值 (独家),从这个随机数生成器的序列中提取。这 nextInt 的一般合同是指定的一个 int 值 range 是伪随机生成并返回的。所有 n 个可能的 int 值以(大约)相等的概率产生。方法 nextInt(int n) 由 Random 类实现,就像通过:

 public int nextInt(int n) {
   if (n <= 0)
     throw new IllegalArgumentException("n must be positive");

   *if ((n & -n) == n)  // i.e., n is a power of 2
     return (int)((n * (long)next(31)) >> 31);*

   int bits, val;
   do {
       bits = next(31);
       val = bits % n;
   } while (bits - val + (n-1) < 0);
   return val;
 }

注意当 n 是 2 的幂时的特定情况。不管你传递给 nextInt 的 n 是什么,函数 next 都会使用相同的输入位调用,并且返回语句只是 n 倍,所以任何时候你发出 2 的幂请求,它都会返回非常确定的。

【讨论】:

  • 我尝试使用一个 Random() 实例并多次生成 nextInt(2^N) ——一切都很好。初始种子值有问题,它以某种方式与这种“特殊情况”相互作用。
  • 很抱歉,但到目前为止,这个解释看起来并不令人信服。如果您查看next(int bits) 的实现,您会发现它基于当前种子,而不仅仅是输入。此外,到目前为止,我无法在桌面上重现奇怪的结果。必须尝试使用​​android
  • 文档中的任何地方都没有真正说明正式定义,但构造函数中生成的种子值几乎可以肯定是基于系统时间(就像大多数伪随机生成器一样)。由于您正在快速声明多个构造,因此每个构造的种子值要么具有很大程度上匹配的高阶位,要么完全匹配。最重要的是,next() 函数会去掉低位,因此如果高位匹配,则种子将变得相同。因此,尽管构造函数声明,next() 将返回相同的值。
  • @Salauyou 很好,做一些调试我发现了几件事。 1)[非常明显],如果你在 Random() 构造函数之间引入一些人工睡眠,你会得到更多随机结果,这意味着你得到的部分是由于“坏”播种,部分是因为 SeaNick 解释的2^N。 2) Random.java 的Android 源代码(构造中的种子和next)与jdk 实现有很大不同,它在android 版本之间也有很大的不同。例如,在我的 4.2.2 中,Random() 构造函数种子的时间以毫秒为单位,而不是纳秒!!!
  • @SeaNick +1。我同意你的观点,对于 2^N,你得到的结果更多地依赖于种子(因为在这种情况下,next 只被调用一次),并且由于我们在实例之间有非常“接近”的种子,所以它使结果看起来随机性要少得多。
猜你喜欢
  • 1970-01-01
  • 2019-06-21
  • 1970-01-01
  • 1970-01-01
  • 2018-12-17
  • 1970-01-01
  • 2016-03-17
  • 2011-11-16
  • 1970-01-01
相关资源
最近更新 更多