我已经将我遇到的一个错误简化为以下几行代码:
int[] vals = new int[8];
for (int i = 0; i < 1500; i++)
vals[new Random(i).nextInt(8)]++;
System.out.println(Arrays.toString(vals));
输出为:[0,0,0,0,0,1310,190,0]
这只是选择连续数字来种子随机然后使用功率为2的nextInt的工件吗?如果是这样,我是否应该注意到这样的其他陷阱,如果没有,我做错了什么? (我不是在寻找上述问题的解决方案,只是对其他什么可能出错的理解)
至于代码,需要几个并行代理具有可重复的随机行为,这些行为恰好从枚举8个元素中选择,只要它们的第一步。一旦我发现了这种行为,种子都来自一个从已知种子创建的主随机对象。在程序的前一个(顺序播种)版本中,所有行为在第一次调用nextInt后迅速分散,因此我花了很长时间才将程序的行为缩小到RNG库,我想避免未来的情况。
答案 0 :(得分:22)
RNG的种子本身应该是随机的。你正在使用的种子只会有一两位不同。
很少有理由在一个程序中创建两个单独的RNG。您的代码不是有意义的情况之一。
只需创建一个RNG并重复使用,就不会有这个问题。
回复mmyers的评论
你碰巧知道java.util.Random 足以解释为什么它选择5 在这种情况下是6?
答案在java.util.Random的源代码中,它是linear congruential RNG。在构造函数中指定种子时,将按如下方式对其进行操作。
seed = (seed ^ 0x5DEECE66DL) & mask;
掩码只保留较低的48位并丢弃其他位。
生成实际随机位时,此种子的操作如下:
randomBits = (seed * 0x5DEECE66DL + 0xBL) & mask;
现在,如果您认为Parker使用的种子是连续的(0-1499),并且它们被使用一次然后丢弃,则前四个种子生成以下四组随机位:
101110110010000010110100011000000000101001110100
101110110001101011010101011100110010010000000111
101110110010110001110010001110011101011101001110
101110110010011010010011010011001111000011100001
请注意,前10位在每种情况下都是缩进的。这是一个问题,因为他只想生成0-7范围内的值(只需要几位),而RNG实现通过将高位向右移位并丢弃低位来实现这一点。这样做是因为在一般情况下,高位比低位更随机。在这种情况下,他们不是因为种子数据很差。
最后,要了解这些位如何转换为我们得到的十进制值,您需要知道当n是2的幂时,java.util.Random会产生一种特殊情况。它请求31个随机位(前31位)来自上述48)的位,将该值乘以n,然后将其向右移位31位。
乘以8(本例中的n值)与左移3位相同。因此,此过程的净效果是将31位28位置向右移动。在上面的4个例子的每一个中,这留下了位模式101(或十进制的5)。
如果我们在一个值之后没有丢弃RNG,我们会看到序列发散。虽然上面的四个序列都以5开头,但每个序列的第二个值分别为6,0,2和4。初始种子的微小差异开始产生影响。
响应更新的问题: java.util.Random是线程安全的,您可以跨多个线程共享一个实例,因此仍然不需要多个实例。如果确实必须有多个RNG实例,请确保它们彼此完全独立,否则您不能相信输出是独立的。
至于为什么你会得到这些效果,java.util.Random并不是最好的RNG。这很简单,非常快,如果你看起来不太近,那就相当随机。但是,如果您在其输出上运行一些serious tests,您会发现它存在缺陷。您可以直观地看到here。
如果您需要更随机的RNG,可以使用java.security.SecureRandom。这有点慢,但它运作正常。但是,对你来说可能有问题的一件事是它不可重复。具有相同种子的两个SecureRandom实例将不会提供相同的输出。这是设计的。
那么还有其他选择吗?这是我插入my own library的地方。它包括3个可重复的伪RNG,比SecureRandom更快,比java.util.Random更随机。我没有发明它们,我只是从最初的C版本移植它们。它们都是线程安全的。
我实施了这些RNG,因为我需要my evolutionary computation code更好的东西。根据我原来的简要回答,这段代码是多线程的,但它只使用一个RNG实例,在所有线程之间共享。