English 中文(简体)
使用连续的数字种子java.util.Random
原标题:
  • 时间:2009-01-08 22:45:37
  •  标签:

我已经将我遇到的一个错误简化为以下代码:

    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的一种偶然现象吗?如果是这样,我还应该注意哪些其他陷阱?如果不是,那么我做错了什么?(我不是在寻求上述问题的解决方案,只是想了解还可能出现什么问题)


丹,分析得很好。由于javadoc很明确地说明了数字是如何计算的,所以这不是一个什么神秘的问题,更多的问题是是否有其他类似的异常需要注意——我没有看到有关连续种子的文档,我希望有经验的java.util.Random用户能指出其他常见的陷阱。

至于代码,需要多个并行代理具有可重复的随机行为,他们碰巧从长度为8的枚举中选择作为他们的第一步。一旦我发现了这种行为,所有随机数的种子都来自于一个从已知种子创建的主随机对象。在程序的前一个(顺序种子)版本中,所有行为很快在第一个nextInt调用后分歧,所以我花了很长时间才将程序的行为缩小到RNG库,我希望避免这种情况在未来发生。

问题回答

尽可能地,RNG的种子应该本身就是随机的。你正在使用的种子只会在一两位上有所不同。

很少有好的理由在一个程序中创建两个独立的RNG。你的代码并不是其中有意义的情况之一。

只需创建一个随机数生成器并重复使用,就不会出现这个问题。

回复 mmyers 的评论:

Do you happen to know java.util.Random well enough to explain why it picks 5 and 6 in this case?

答案在java.util.Random的源代码中,它是一个线性同余随机数生成器。当你在构造函数中指定一个种子时,它会按以下方式操作。

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个随机位(上述48个位中的前31个),将该值乘以n,然后将其右移31位。

乘以8(在这个例子中的n值)就相当于将左移3位。因此,这个过程的净效应是将31位向右移动28个位置。在上面4个例子中的每一个中,这将留下位模式101(或十进制中的5)。

如果我们不仅仅在一个值后抛弃RNGs,我们会看到序列分散。虽然以上四个序列都是以5开头,但每个序列的第二个值分别是6、0、2和4。种子的微小差异开始产生影响。

作为对更新问题的回答:java.util.Random是线程安全的,您可以在多个线程之间共享一个实例,因此仍然不需要具有多个实例。如果您确实需要多个RNG实例,请确保它们彼此完全独立地进行种子处理,否则您无法相信输出是独立的。

关于为什么会出现这种效果,java.util.Random并不是最好的随机数生成器。它很简单,速度相当快,如果你不仔细查看的话,似乎是随机的。然而,如果你对它的输出进行一些严格的测试,你会发现它有缺陷。你可以在这里通过视觉方法看到这些缺陷。

如果您需要更加随机的RNG,您可以使用java.security.SecureRandom。它的速度稍微慢一些,但它可以正常工作。但是可能会有一个问题,它是不可重复的。使用相同的种子生成的两个SecureRandom实例将不会得到相同的输出结果。这是有意设计的。

还有哪些其他选择?这就是我推荐我的自己的库的地方。它包括3个可重复的伪随机数生成器,比SecureRandom更快,比java.util.Random更随机。我没有发明它们,我只是从原始的C版本移植了它们。它们都是线程安全的。

我实现了这些RNG,因为我需要一个更好的进化计算代码。根据我的最初简短回答,这个代码是多线程的,但它只使用一个RNG实例,共享给所有线程。





相关问题
热门标签