Java中setSeed后的第一个随机数总是相似的

为了给出一些上下文,我一直在用Java编写一个基本的Perlin噪声实现,当实现播种时,我遇到了一个我无法解释的错误。

为了每次为相同的种子生成相同的随机权向量,无论查询哪组坐标的噪声水平,以什么顺序,我都基于原始种子和原始种子的组合生成新的种子( newSeed )加权vector的坐标,并通过运行将其用作加权vector随机化的种子:

 rnd.setSeed(newSeed); weight = new NVector(2); weight.setElement(0, rnd.nextDouble() * 2 - 1); weight.setElement(1, rnd.nextDouble() * 2 - 1); weight.normalize() 

NVector是vectormath的自制课程。

但是,运行时,程序产生非常糟糕的噪音: 非常糟糕的噪音,与垂直条纹

经过一番挖掘之后,我发现每个向量的第一个元素非常相似(所以在每个setSeed()调用之后的第一个nextDouble() setSeed()调用)导致向量网格中每个向量的第一个元素是相似的。

这可以通过运行来certificate:

 long seed = Long.valueOf(args[0]); int loops = Integer.valueOf(args[1]); double avgFirst = 0.0, avgSecond = 0.0, avgThird = 0.0; double lastfirst = 0.0, lastSecond = 0.0, lastThird = 0.0; for(int i = 0; i<loops; i++) { ran.setSeed(seed + i); double first = ran.nextDouble(); double second = ran.nextDouble(); double third = ran.nextDouble(); avgFirst += Math.abs(first - lastfirst); avgSecond += Math.abs(second - lastSecond); avgThird += Math.abs(third - lastThird); lastfirst = first; lastSecond = second; lastThird = third; } System.out.println("Average first difference.: " + avgFirst/loops); System.out.println("Average second Difference: " + avgSecond/loops); System.out.println("Average third Difference.: " + avgSecond/loops); 

在程序参数指定的种子范围内调用setSeed()方法后,生成的第一个,第二个和第三个随机数之间的平均差值; 对我来说这些结果是:

 C:\java Test 462454356345 10000 Average first difference.: 7.44638117976783E-4 Average second Difference: 0.34131692827329957 Average third Difference.: 0.34131692827329957 C:\java Test 46245445 10000 Average first difference.: 0.0017196011123287126 Average second Difference: 0.3416750057190849 Average third Difference.: 0.3416750057190849 C:\java Test 1 10000 Average first difference.: 0.0021601598225344998 Average second Difference: 0.3409914232342002 Average third Difference.: 0.3409914232342002 

在这里你可以看到,第一次的平均差异比其他的要小得多,并且看起来随着更高的种子而下降。

因此,在设置权向量之前,通过向nextDouble()添加一个简单的虚拟调用,我能够修复我的perlin噪声实现:

 rnd.setSeed(newSeed); rnd.nextDouble(); weight.setElement(0, rnd.nextDouble() * 2 - 1); weight.setElement(1, rnd.nextDouble() * 2 - 1); 

导致:

在这里输入图像说明

我想知道为什么在第一次调用nextDouble() (我没有检查其他types的随机性)时出现这种不好的变化,并且/或者提醒人们注意这个问题。

当然,这可能只是我的一个实现错误,如果我指出的话,我会很感激。

Random类被devise成伪随机数的低开销源。 但是,“低开销”实施的结果是,从统计的angular度来说,数字stream的特性还有很长的路要走。 你遇到了一个不完美之处。 Random被logging为线性同余发生器,并且这种发生器的属性是众所周知的。

有很多方法来处理这个问题。 例如,如果你小心,你可以隐藏一些最明显的“穷人”的特点。 (但是build议您运行一些统计testing,在第二个图像中添加的噪声中看不到非随机性,但仍可能存在。)

或者,如果您想要保证良好统计属性的伪随机数,那么您应该使用SecureRandom而不是Random 。 它具有明显较高的开销,但是你可以放心,许多“聪明人”将花费大量的时间在algorithm的devise,testing和分析上。

最后,创build一个Random的子类相对简单,它使用一个替代algorithm来生成数字; 看链接 。 问题是你必须select(或devise)并实现一个合适的algorithm。


称这是一个“ 问题 ”是值得商榷的。 这是一个众所周知和理解LCGs的财产,使用LCGs是一个concierge工程的select。 人们想要低开销的PRNG,但是低开销的PRNG具有差的性能。 TANSTAAFL。

当然,这不是Oracle想要在Random改变的东西。 事实上,在Random类的javadoc中明确地说明了不改变的原因。

“为了保证这个属性,为Random类指定了特定的algorithm,为了Java代码的绝对可移植性,Java实现必须使用这里所示的Random类的所有algorithm。

这是已知的问题。 类似的种子会产生类似的less数第一个值。 随机不是真的被devise成这样使用。 你应该创build一个好种子的实例,然后生成适度大小的“随机”数字序列。

你目前的解决scheme是好的 – 只要它看起来不错,速度足够快。 您也可以考虑使用散列/混合函数来devise解决您的问题(然后,可选地使用输出作为种子)。 例如,请参阅: 2D噪声生成的参数随机函数

将你的setSeed移出循环。 Java的PRNG是一个线性同余生成器,因此为它连续赋值是保证给出在循环迭代中相关的结果。

附录

我赶了出去开会,现在有时间来说明我上面说的话。

我写了一个实现了Schrage的便携式素数模乘法线性同余发生器的小Ruby脚本。 我实例化LCG的两个副本,两者的值均为1.但是,在输出循环的每次迭代中,我都根据循环索引重新设置第二个副本。 代码如下:

 # Implementation of a Linear Congruential Generator (LCG) class LCG attr_reader :state M = (1 << 31) - 1 # Modulus = 2**31 - 1, which is prime # constructor requires setting a seed value to use as initial state def initialize(seed) reseed(seed) end # users can explicitly reset the seed. def reseed(seed) @state = seed.to_i end # Schrage's portable prime modulus multiplicative LCG def value @state = 16807 * @state % M # return the generated integer value AND its U(0,1) mapping as an array [@state, @state.to_f / M] end end if __FILE__ == $0 # create two instances of LCG, both initially seeded with 1 mylcg1 = LCG.new(1) mylcg2 = LCG.new(1) puts " default progression manual reseeding" 10.times do |n| mylcg2.reseed(1 + n) # explicitly reseed 2nd LCG based on loop index printf "%d %11d %f %11d %f\n", n, *mylcg1.value, *mylcg2.value end end 

这里是它产生的输出:

  default progression manual reseeding 0 16807 0.000008 16807 0.000008 1 282475249 0.131538 33614 0.000016 2 1622650073 0.755605 50421 0.000023 3 984943658 0.458650 67228 0.000031 4 1144108930 0.532767 84035 0.000039 5 470211272 0.218959 100842 0.000047 6 101027544 0.047045 117649 0.000055 7 1457850878 0.678865 134456 0.000063 8 1458777923 0.679296 151263 0.000070 9 2007237709 0.934693 168070 0.000078 

这些列是迭代数字,后面是由LCG生成的基础整数,缩放到范围(0,1)时的结果。 左边的一列显示了LCG的自然进程,当允许自行进行的时候,右边的列显示了每次迭代时的情况。