Java 为什么使用相似种子时初始随机数相似?
我发现使用Java的random类生成随机数有些奇怪。 基本上,如果使用close seeds创建多个随机对象(例如,在1到1000之间),每个生成器生成的第一个值几乎相同,但下一个值看起来很好(我没有进一步搜索) 以下是第一次生成的两个双精度,种子数从0到9:Java 为什么使用相似种子时初始随机数相似?,java,random,Java,Random,我发现使用Java的random类生成随机数有些奇怪。 基本上,如果使用close seeds创建多个随机对象(例如,在1到1000之间),每个生成器生成的第一个值几乎相同,但下一个值看起来很好(我没有进一步搜索) 以下是第一次生成的两个双精度,种子数从0到9: 0.730967787376657 0.24053641567148587 1 0.7308781907032909 0.41008081149220166 2 0.7311469360199058 0.901447624030054
- 0.730967787376657 0.24053641567148587
- 1 0.7308781907032909 0.41008081149220166
- 2 0.7311469360199058 0.9014476240300544
- 3 0.731057369148862 0.07099203475193139
- 4 0.7306094602878371 0.918714013855101
- 5 0.730519863614471 0.08825840967622589
- 6 0.7307886238322471 0.5796252073129174
- 7 0.7306990420600421 0.7491696031336331
- 8 0.73025113910172 0.5968915822372118
- 9 0.7301615514268123 0.7664359929590888
- 991 0.7142160704801332 0.9453385235522973
- 992 0.7109015598097105 0.21848118381994108
- 993 0.7108119780375055 0.38802559454181795
- 994 0.7110807233541204 0.8793923921785096
- 995 0.7109911564830766 0.048936787999225295
- 996 0.7105432327208906 0.896658767102804
- 997 0.7104536509486856 0.066201629235198
- 998 0.7107223962653005 0.5575699754613725
- 999 0.7106328294922568 0.72711437112820883
- 1000 0.7101849056320707 0.574836350385667
谢谢。通过生成随机种子(例如,在System.currentTimeMillis()或System.nanoTime()上使用一些数学函数生成种子),您可以获得更好的随机结果。也可以查看更多信息我不会把这称为“问题” 另外,您知道这个问题是只针对第一个值(或前几个值),还是更一般,应该避免使用相近的种子 连续数字之间的相关模式是非加密PRNG的常见问题,这只是一种表现形式。相关性(严格自相关)是算法的数学基础中固有的。如果你想了解这一点,你应该先阅读Knuth的《计算机编程艺术》第3章的相关部分 如果您需要不可预测性,您应该为
random
使用(真)随机种子。。。或者让系统为你挑选一个“非常随机”的;e、 g.使用无参数构造函数。或者更好的方法是,使用真实随机数源或加密质量PRNG,而不是random
记录在案:
Random()
的实现是从纳秒时钟开始的,用一个“uniquifier”序列进行异或。“uniquifier”序列是使用不同乘法器的LCG,其状态为静态。这是为了避免种子的自相关李>
这是伪随机种子的一种相当典型的行为——它们不需要提供完全不同的随机序列,它们只提供了一种保证,即如果使用相同的种子,您可以再次获得相同的序列 这种行为的发生是因为PRNG的数学形式——Java one使用了一个线性同余生成器,所以您只看到通过一轮线性同余生成器运行种子的结果。这还不足以完全混合所有的位模式,因此您可以看到类似种子的类似结果
您最好的策略可能只是使用非常不同的种子-一种选择是通过散列您当前使用的种子值来获得这些种子。您最好下载并阅读
随机
源代码,以及一些关于伪随机生成器的论文,但这里是一些相关部分的来源。首先,有三个恒定参数控制算法:
private final static long multiplier = 0x5DEECE66DL;
private final static long addend = 0xBL;
private final static long mask = (1L << 48) - 1;
您提供了一个非常接近于零的种子,因此设置的初始种子值由乘法器控制,当二者相加时。在所有种子接近零的测试用例中,内部使用的种子大约为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
看到类似的初始输出的原因是,因为下一个整数的计算只涉及乘法和加法,所以下一个整数的大小是
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));
}