Random.nextInt(int)[略微偏]

时间:2013-07-24 10:03:23

标签: java random

即,它永远不会连续生成超过16个偶数,并带有一些特定的upperBound参数:

Random random = new Random();

int c = 0;
int max = 17;
int upperBound = 18;

while (c <= max) {
    int nextInt = random.nextInt(upperBound);
    boolean even = nextInt % 2 == 0;
    if (even) {
        c++;
    } else {
        c = 0;
    }
}

在此示例中,代码将永远循环,而当upperBound为例如16时,代码将很快终止。

这种行为的原因是什么?方法的javadoc中有一些注释,但我没理解它们。


UPD1 :代码似乎以奇数上限终止,但可能会遇到偶数


UPD2 : 我修改了代码以捕获评论中建议的c的统计信息:

Random random = new Random();

int c = 0;
long trials = 1 << 58;
int max = 20;
int[] stat = new int[max + 1];

while (trials > 0) {
    while (c <= max && trials > 0) {
        int nextInt = random.nextInt(18);
        boolean even = nextInt % 2 == 0;
        if (even) {
            c++;
        } else {
            stat[c] = stat[c] + 1;
            c = 0;
        }
        trials--;
    }
}

System.out.println(Arrays.toString(stat));

现在它尝试在行中达到20个平均值 - 以获得更好的统计信息,而upperBound仍然是18

结果结果不仅令人惊讶:

[16776448, 8386560, 4195328, 2104576, 1044736, 
 518144, 264704, 132096, 68864, 29952, 15104, 
 12032, 1792, 3072, 256, 512, 0, 256, 0, 0]

首先它按预期减少2倍,但请注意最后一行!在这里它变得疯狂,捕获的统计数据似乎非常奇怪。

以下是对数刻度的条形图:

c statistics

c如何获得价值17 256次又是另一个谜团

3 个答案:

答案 0 :(得分:5)

http://docs.oracle.com/javase/6/docs/api/java/util/Random.html

  

此类的实例用于生成流的   伪随机数。该类使用48位种子,该种子经过修改   使用线性同余公式。 (见唐纳德克努特,艺术   计算机编程,第3卷,第3.2.1节。)

     

如果使用相同的种子创建了两个Random实例,那么   为每个方法调用相同的方法调用,它们将生成和   返回相同的数字序列。 [...]

它是 - 随机数生成器。这意味着您实际上并没有掷骰子,而是使用公式根据当前随机值计算下一个“随机”值。为了产生随机化的假象,使用seed。种子是公式中用于生成随机值的第一个值。

显然javas随机实现(“公式”),连续生成的偶数不超过16个。

这种行为是seed通常随时间初始化的原因。根据你何时启动你的程序,你会得到不同的结果。

这种方法的好处是可以产生可重复的结果。如果你有一个游戏生成“随机”地图,你可以记住种子,如果你想再次播放它,重新生成相同的地图。

对于真正的随机数,某些操作系统会提供特殊设备,这些设备会从鼠标移动或网络流量等外部事件中生成“随机性”。但是我不知道如何利用java。

来自 secureRandom 的Java文档:

  

许多SecureRandom实现采用伪随机的形式   数字生成器(PRNG),这意味着它们使用确定性   算法从真随机种子产生伪随机序列。   其他实现可能产生真正的随机数,还有其他实现   可以结合使用这两种技术。

请注意,secureRandom也可以保证 true 随机数。

为什么更换种子没有帮助

假设随机数只有0-7。 现在我们使用以下公式生成下一个“随机”数字:

 next = (current + 3) % 8

序列变为0 3 6 1 4 7 2 5

如果你现在拿种子3你所做的就是改变起点。

在这个仅使用前一个值的简单实现中,每个值只能在序列换行并再次启动之前出现一次。否则会有一个无法到达的部分。

E.g。想象一下序列0 3 6 1 3 4 7 2 5。数字0,4,7,2 and 5永远不会产生多次(因为它们可能永远不会产生种子),因为一旦序列循环3,6,1,3,6,1,......。

可以将简化的伪随机数生成器视为范围内所有数字的排列,并使用种子作为起点。如果它们更高级,则必须使用可能多次出现相同数字的列表替换排列。

更复杂的生成器可以具有内部状态,允许在序列中多次出现相同的数字,因为状态允许生成器知道继续的位置。

答案 1 :(得分:2)

Random的实现使用简单的线性同余公式。这些公式具有自然周期性和它们产生的序列中的各种非随机模式。

你所看到的是这些模式之一的人工制品......没有任何故意。这不是偏见的一个例子。相反,它是auto-correlation的一个例子。

如果您需要更好(更“随机”)的数字,则需要使用SecureRandom而不是Random

“为什么以这种方式实施的答案是” ......性能。对Random.nextInt的调用可以在数十或数百个时钟周期内完成。对SecureRandom的调用可能至少慢2个数量级,可能更多。

答案 2 :(得分:1)

为了便于移植,Java指定实现必须使用java.util.Random的劣质LCG方法。对于像复杂模拟或蒙特卡罗方法这样的随机数的任何严重使用,这种方法是完全不可接受的。使用具有更好的PRNG算法的附加库,如Marsaglia的MWC或KISS。 Mersenne Twister和Lagged Fibonacci发电机通常都可以。

我确信这些算法都有Java库。我有一个带有Java绑定的C库,如果它适合你:ojrandlib