【问题标题】:Random long, can it be twice in a row the same number随机长,可以连续两次同一个数吗
【发布时间】:2014-03-05 06:16:19
【问题描述】:

我想知道 Random 类的当前 java 1.7 实现,下面的代码是否有可能生成两次相同的随机长?

Random rand = new Random((long) "some seed".hashCode());
while(rand.nextLong() != rand.nextLong()){

}

System.out.println("Will this text ever be on the console?");

nextLong() 和 next() 的 Java 源代码;

public long nextLong(){
    return ((long) next(32) << 32) + next(32);
}

protected synchronized int next(int bits){
    seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
    return (int) (seed >>> (48 - bits));
}

我会用 false 回答这个问题,因为我认为 java 使用的随机方法不会在 2^48 周期内重复相同的数字,因此它永远不会连续生成两个相同的数字。这是正确的吗?

【问题讨论】:

  • 理论上是可以的。您有 1:281,474,976,710,656 (2^48) 的机会获得特定数字。这意味着您有 1:79,228,162,514,264,337,593,543,950,336 ((2^48)^2) 两次获得相同的数字(或任何单独的两个数字集,技术上)。所以,理论上,是的。您可能需要运行该循环几个世纪才能中断,但它最终会发生。
  • 我怀疑你是对的,rand.nextLong 的伪随机算法永远不会连续产生两个相同的值,除非重新播种。但对于“真正的”RNG 或以不同方式得出其值的 PRNG,情况并非如此。
  • @Deactivator2 - Rolf 的想法是该算法是一个 随机数生成器,如果 long 值本质上是生成器内部存储的主要值,它永远不会重复,为伪随机算法的周期时间。我怀疑他可能是对的,尽管需要检查算法的内部才能确定。
  • @HotLicks 忽略此评论,我相信您实际上是对的。我错误地认为seed 因某种原因没有被结转,但该值在每次调用next() 时都保持不变。在这种情况下,我确实相信不可能两次生成相同的值,除非有一个字面上的单个值适合该种子方程会吐出相同的值。
  • @Deactivator2 - 算法反复修改seed,从前一个值生成下一个值。然而,长值是从两个连续的 32 位值产生的,而 32 位值是从显然是 48 位的值产生的,(希望)循环时间约为 2**48。因此,理论上该算法有可能快速连续生成两个低 32 位相同的值(尽管我怀疑该算法的细节会阻止这种情况发生)。 IOW,一位数学家也许能够证明有机会重复,但没有凡人程序员可以。

标签: java random


【解决方案1】:

想出一个比我以前“更长”的答案:

你已经链接了实现,它看起来像:

public long nextLong(){
    return ((long) next(32) << 32) + next(32);
}

所以,很明显,一个随机数调用了 2 次 next(32)。 这意味着,如果next(32) 结果,2 个随机数将相等 相同数字的 4 倍,因为函数的其余部分是“硬编码”的。

查看next()函数,我们可以看到如下:

protected synchronized int next(int bits){
    seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
    return (int) (seed >>> (48 - bits));
}

可以简单地忽略返回部分,因为同样:SAME 种子会导致 到相同的返回值 - 否则你的 CPU 坏了。

所以,总的来说:我们只需要专注于这条线

seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);

如果这将导致相同的种子,四次,已生成 2 个随机数, 是相等的。

(注意:可以排除 a,b,a,b 之类的序列以产生相同的结果。帖子足够长,我跳过那部分。)


首先,让我们消除&lt;&lt; 48 部分。那是什么意思?给定的数字 (1) 将左移 48 次。所以二进制0...01 将变成1000000000000000000000000000000000000000000000000(48 个零) 然后,减去一个,所以你会得到0111111111111111111111111111111111111111111111111(47个)

让我们看一下该等式的第一部分:

(seed * 0x5DEECE66D[L] + 0xB[L])

注意,结尾的 [L] 只会导致它是一个长值而不是整数。

所以,用二进制的话来说,这意味着:

seed * 10111011110111011001110011001101101 + 1011

毕竟函数看起来像

seed = (seed * 10111011110111011001110011001101101 + 1011) & (0111111111111111111111111111111111111111111111111)

(我省略了第一个值的前导零)

那么,&amp; (0111111111111111111111111111111111111111111111111) 做了什么?

按位和运算符,基本上比较两个二进制数的每个位置。并且只有当它们都为“1”时,生成的二进制数中的位置才会为 1。

这就是说,等式(seed * 10111011110111011001110011001101101 + 1011) 中距离右侧大于 48 的每一位都将被忽略

第 49 位等于 2^49562949953421312 decimal - 这意味着 & (0111111111111111111111111111111111111111111111111) 基本上只是说 MAXIMUM 结果可以是562949953421312 - 1。 因此,562949953421312 将再次产生 0,而不是结果 562949953421313 将产生 1,依此类推。

我上面写的所有东西都可以很容易地验证:

虽然下面的代码会产生随机的种子 *11*:

private Long seed = 0L;

protected synchronized int next(int bits){
    seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
    System.out.println(seed);
    return (int) (seed >>> (48 - bits));
}

可以对种子进行逆向工程,并且还可以使用数字 562949953421312L 从非 0 种子中获取种子 11。

private Long seed = 562949953421312L - 0xBL / 0x5DEECE66DL;

protected synchronized int next(int bits){
    seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
    System.out.println(seed);
    return (int) (seed >>> (48 - bits));
}

所以,您会看到:种子 562949953421312 等于种子 0

更容易证明:

Random r = new Random(0L);
Random r2 = new Random(562949953421312L);

if (r.nextLong()==r2.nextLong()){
    System.out.println("Equal"); //You WILL get this!
}

当然是连续的:

Random r3 = new Random(1L);
Random r4 = new Random(562949953421313L);

if (r3.nextLong()==r4.nextLong()){
    System.out.println("Equal");
}

为什么这个“神奇数字”(562949953421312L)很重要?

假设,我们从种子 0 开始。

第一个新种子将是:0 * 10111011110111011001110011001101101 + 1011 = 1011 (dec: 11)

下一个种子是:1011 * 10111011110111011001110011001101101 + 1011 = 100000010010100001011011110011010111010 (dec: 277363943098)

下一个种子(调用 3)将是:100000010010100001011011110011010111010 * 10111011110111011001110011001101101 + 1011 = 10000100101000000010101010100001010100010011100101100100111101 (dec 2389171320405252413)

所以,超出了562949953421312L的最大数量,这将导致随机数小于上述计算值。

此外,添加1011 将导致结果在奇数和偶数之间交替。 (不确定 real 的含义 - 加 1 也可以,恕我直言)

因此,生成 2 个种子(不是随机数)可确保它们不相等,因为已选择特定的“溢出”点 - 并且添加 MAXIMUM 值 (562949953421312L) 不足以在 2 内达到相同的数字几代人。

当 2 次相同的种子是不可能的时,4 次也是不可能的,这意味着 nextLong() 函数永远不会在 n 和 n+1 代中返回相同的值。

我不得不说,我想证明相反的情况。从统计的角度来看,相同数字的 2 倍是可能的 - 但也许这就是它被称为伪随机性的原因:)

【讨论】:

    【解决方案2】:

    不,用这种算法连续获得两个相同的长整数是不可能的。

    当人们在写关于数学和其他魔法的长篇文章时,我走上了代码猴子路线,并在周末暴力破解了 2^48 个可能的种子。对于任何种子,没有两个连续生产的多头是相等的。


    long int seed = 0;
    
    int next(int bits){
        seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
        return (int) (seed >> (48 - bits));
    }
    long int nextLong(){
        return ((long int) next(32) << 32) + next(32);
    }
    
    int main(int argc, char** argv) {
      long int step = atoi(argv[1]);
      long int i = step << 32;
      long int end = (step+1) << 32;
      while(i < end) {
        seed = i;
        if(nextLong() == nextLong()) {
          printf("Found seed %ld\n", i);
          return 0;
        }
        ++i;
      }
      printf("No seed in %ld\n", step);
      return 1;
    }
    

    然后

    echo {0..65535} | xargs -n 1 -P 12 ./executable
    

    【讨论】:

    • @MooingDuck 它测试每一对。您会注意到它不会比较 0,11,22,3 等,而只是 0,1。以下序列仅根据当前种子确定,因此如果有一个产生0 1 1 2 2.. 的种子,那么也有一个产生1 1 2 2.. 的种子,并且1,1 将被捕获在那里。检查每个种子的第一对相当于检查每个种子产生的每一对序列。
    • 我的错误,我没有仔细阅读您的代码,应该可以正常工作。我假设您使用的是另一种算法,看起来有点相似,但运行速度应该是原来的两倍。
    • 整洁。哪种算法?
    • long prev=nextLong(); for(long i=0; i&lt;LONG_MAX; ++i) { long cur=nextLong();if(cur==prev)return i;cur=prev;}。每个种子只调用一次nextLong,而不是两次。
    • 再说一次,我还没弄清楚step 是干什么用的,你是并行运行的吗?这也是个好主意。等等,不是有 2^64 种可能的种子吗?
    【解决方案3】:

    这取决于您要问的问题。

    正如其他人所说:伪随机数生成器的标准公式在重复之前充分探索了它们的值空间。

    但是:在大多数应用程序中,我们不会使用 PRNG 输出的全部范围。我们通过将其划分或截断到与我们试图解决的问题相匹配的范围来减少它。事实上,我们这样做的大多数方式都会产生一系列数字,其中可能包括立即重复。

    当基础公式使用更多位进行计算时,即使您只是使用整数随机数,情况也是如此。

    所以:理论上,如果您直接查看 PRNG 的输出,答案是“可能不是”。实际上,答案是“无论哪种方式都不要指望它。”

    【讨论】:

      【解决方案4】:

      我不这么认为。我相信生成器使用下一个数字作为后续数字的种子。所以,如果你得到一个值一次,如果它要重复,你的数字生成器就会陷入循环。

      但是,许多应用程序都在寻找某个范围内的数字,这使得重复成为可能,因为后续数字可能具有与该范围模数相同的值。

      编辑:由于您包含next 的源代码,您可以看到,如果它返回相同的数字,它总是会返回相同的数字。因此,您将被困在一个值的循环中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-08
        • 1970-01-01
        • 2015-01-29
        • 2016-02-16
        • 1970-01-01
        • 1970-01-01
        • 2011-09-16
        • 2021-12-21
        相关资源
        最近更新 更多