我发现了使用Java的Random类生成随机数的奇怪之处。 基本上,如果使用近似种子(例如1到1000之间)创建多个Random对象,则每个生成器生成的第一个值几乎相同,但下一个值看起来很好(我没有进一步搜索)。
这是两个第一个生成的双子种子,从0到9:
从991到1000:
这是一个显示种子从0到100,000生成的第一个值的图。
基于种子生成的第一个随机双精度:
我搜索了有关这方面的信息,但我没有看到任何涉及这个确切问题的内容。我知道LCG算法有很多问题,但我不知道这个问题,我想知道这是否是一个已知的问题。
而且,你知道这个问题是仅针对第一个值(或前几个值),还是更常见且使用密切种子应该避免?
感谢。
答案 0 :(得分:15)
通过下载和阅读Random
来源,以及有关伪随机生成器的一些文章,您将得到最好的服务,但这里是源代码的一些相关部分。首先,有三个控制算法的常量参数:
private final static long multiplier = 0x5DEECE66DL;
private final static long addend = 0xBL;
private final static long mask = (1L << 48) - 1;
乘数大约为2 ^ 34并且更改,掩码为2 ^ 48 - 1,并且此分析的加数非常接近0。
使用种子创建Random时,构造函数会调用setSeed
:
synchronized public void setSeed(long seed) {
seed = (seed ^ multiplier) & mask;
this.seed.set(seed);
haveNextNextGaussian = false;
}
你提供的种子非常接近于零,因此当两者被“或”在一起时,设置的初始种子值由multiplier
支配。在种子接近零的所有测试案例中,内部使用的种子大约为2 ^ 34;但很容易看出,即使您提供了非常大的种子数,类似的用户提供的种子也会产生类似的内部种子。
最后一块是next(int)
方法,它实际上根据当前种子生成所请求长度的随机整数,然后更新种子:
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
这被称为“线性同余”伪随机生成器,意味着它通过将当前种子乘以常数乘数然后添加常数加数(然后屏蔽以取低48位,生成每个连续种子)这个案例)。发电机的质量由乘数和加数的选择决定,但所有这些发电机的输出可以根据当前输入轻松预测,并在重复之前有一段设定的时间(因此建议不要在敏感时使用它们)应用)。
你看到nextDouble
类似种子的类似初始输出的原因是,因为下一个整数的计算只涉及乘法和加法,所以下一个整数的大小不会受差异的影响太大在低位。计算下一个double意味着根据种子计算一个大整数并将其除以另一个(常数)大整数,结果的大小主要受整数幅度的影响。
重复计算下一个种子将放大种子低位的差异,因为常数乘法器重复乘法,并且因为48位掩码每次抛出最高位,直到最终你看到了什么看起来像是一种均匀的传播。
答案 1 :(得分:4)
我不会称之为“问题”。
而且,你知道这个问题是仅针对第一个值(或前几个值),还是更常见且使用密切种子应该避免?
连续数字之间的相关模式是非加密PRNG的常见问题,这只是一种表现形式。相关性(严格自相关)是算法背后的数学中固有的。如果你想了解这一点,你应该首先阅读Knuth的计算机编程艺术第3章的相关部分。
如果你需要不可预测性,你应该为Random
使用(真)随机种子......或者让系统为你挑选一个“非常随机”的种子;例如使用no-args构造函数。或者更好的是,使用真正的随机数源或加密质量的PRNG而不是Random
。
记录:
Random()
的实现是从纳秒时钟播种的,XORed带有'uniquifier'序列。 'uniquifier'序列是LCG,它使用不同的乘数,其状态是静态的。这是为了避免种子的自相关...... 答案 2 :(得分:2)
这是伪随机种子的一种相当典型的行为 - 它们不需要提供完全不同的随机序列,它们只能保证如果使用相同的种子,可以再次获得相同的序列。
行为发生的原因是PRNG的数学形式--Java使用线性同余生成器,因此您只是看到通过一轮线性同余生成器运行种子的结果。这还不足以完全混淆所有位模式,因此您会看到类似种子的类似结果。
您最好的策略可能只是使用非常不同的种子 - 一种选择是通过散列您当前使用的种子值来获取这些种子。
答案 3 :(得分:-1)
通过制作随机种子(例如,使用System.currentTimeMillis()或System.nanoTime()上的一些数学函数进行种子生成),您可以获得更好的随机结果。另请查看here以获取更多信息