Random.nextBoolean() 总是 Returns True 无论种子如何

Random.nextBoolean() Always Returns True No Matter the Seed

当我 运行 下面的代码时,无论我在 for 循环中使用什么范围,代码总是打印出 true 十次。

public static void main(String[] args) 
{
    Random bool = new Random();

    for (int i = 0; i < 10; i++) {
        bool.setSeed(i);
        System.out.println(bool.nextBoolean());
    }
}

但是,如果我对代码稍作改动,让随机生成器 运行 在打印前使用 nextBoolean() 函数一次,我会得到 truefalse 在我更改 for 循环的范围时改变的输出中:

public static void main(String[] args) 
{
    Random bool = new Random();

    for (int i = 0; i < 10; i++) {
        bool.setSeed(i);
        bool.nextBoolean(); //Only change
        System.out.println(bool.nextBoolean());
    }
}

在我看来,nextBoolean() 函数在第一次执行时总是 returns true,这种行为有什么原因吗?

原因在setSeed方法的API中找到:

The implementation of setSeed by class Random happens to use only 48 bits of the given seed.

事实上,您作为种子值提供的 long 乘以一个固定值(在 Random class 中私下定义),然后只有最低有效的 48 位是经过考虑的。尽管这个乘数很大,但因为您的 i 值序列都是连续的,所以它们都会产生数值相似的种子值。因此,前几千个值实际上被视为与 nextBoolean 方法具有相同的值,并且您得到完全相同的 initial 布尔值。再次调用 nextBoolean(无需再次调用 setSeed)将重新乘以种子值,因此您很快就不会看到相同的模式。

如果您确实调用了 setSeed 方法,您应该只需要调用它一次,并且应该在循环之外进行。但是 Random class 完全可以选择自己的种子值,所以我建议你根本不要调用 setSeed 除非你知道你为什么这样做。

所以基本上 nextBoolean 方法只能 return truefalseseed 个值的总数可以是 [Long.MIN_VALUE, Long.MAX_VALUE]。所以,你可以假设这些种子的一半你会得到 true 而另一半你会得到 false.

现在,当您迭代 10 个数字时,对于这 10 个种子,您获得的值可能是 true。当您尝试更大的范围时,您更有可能获得两个值的均等分布。

现在每次调用 nextBoolean() 时,都会使用 (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1) 将种子更新为其他值。所以如果当前种子是 1,下一个种子将是 25214903916,在那里你可以得到 truefalse(你不知道)。这就是为什么有时在循环中调用 nextBoolean() 两次时会得到 false 的原因。毕竟是伪随机数生成器

顺便说一句,你真的不需要调用 setSeed() 方法。该方法仅用于将种子重置为特定值。 Random class 实例本身将以种子值开始,并在每次从中获取值时更新它。你不用担心。

如果你看到 Random class 的代码,这是他们第一次分配种子的方式:

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;
    }
}

所以,你应该把任务留给那个。

这是我们称它们为 "psuedorandom" 数字的原因。每个随机调用背后都有一个复杂且通常不可逆但仍然确定的函数;通常是一个简单的元胞自动机。如此多的随机化种子的 return 为 true 是此函数的产物;为了确保您得到的东西可以说是随机的,我建议使用较大的变体数字,例如 System.nanoTime().