Java中setSeed之后的第一个随机数总是相似的 [英] First random number after setSeed in Java always similar

查看:201
本文介绍了Java中setSeed之后的第一个随机数总是相似的的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

To give some context, I have been writing a basic Perlin noise implementation in Java, and when it came to implementing seeding, I had encountered a bug that I couldn't explain.

为了每次为相同的种子生成相同的随机权重向量,无论查询哪个坐标的噪声级别以及按什么顺序,我都生成了一个新的种子( newSeed ),基于原始种子和权重向量坐标的组合,并通过运行它作为权重向量随机化的种子:

In order to generate the same random weight vectors each time for the same seed no matter which set of coordinates' noise level is queried and in what order, I generated a new seed (newSeed), based on a combination of the original seed and the coordinates of the weight vector, and used this as the seed for the randomization of the weight vector by running:

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

其中 NVector 是一个自制的矢量数学类。

Where NVector is a self-made class for vector mathematics.

然而,当运行时,程序产生非常糟糕的噪音:

However, when run, the program generated very bad noise:

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

After some digging, I found that the first element of each vector was very similar (and so the first nextDouble() call after each setSeed() call) resulting in the first element of every vector in the vector grid being similar.

这可以通过运行来证明:

This can be proved by running:

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

在<$ c $之后生成的第一个,第二个和第三个随机数之间的平均差异c> setSeed()方法已经被程序参数指定的一系列种子调用;这对我来说是返回这些结果:

Which finds the average difference between the first, second and third random numbers generated after a setSeed() method has been called over a range of seeds as specified by the program's arguments; which for me returned these results:

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

在这里你可以看到第一个平均差异是显着小于其余的,并且看起来随着更高的种子而减少。

Here you can see that the first average difference is significantly smaller than the rest, and seemingly decreasing with higher seeds.

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

As such, by adding a simple dummy call to nextDouble() before setting the weight vector, I was able to fix my perlin noise implementation:

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

结果:

我想知道为什么这么糟糕第一次调用 nextDouble()(我没有检查过其他类型的随机性)的变化发生和/或提醒人们注意这个问题。

I would like to know why this bad variation in the first call to nextDouble() (I have not checked other types of randomness) occurs and/or to alert people to this issue.

当然,这可能只是我的一个实施错误,如果有人向我指出,我会很高兴。

Of course, it could just be an implementation error on my behalf, which I would be greatful if it were pointed out to me.

推荐答案

随机 类被设计为伪随机数的低开销源。但是,低开销实现的结果是,从统计角度来看,数字流具有很长的完美特性。你遇到了一个不完美的地方。 Random 被记录为线性同余生成器,并且这些生成器的属性是众所周知的。

The Random class is designed to be a low overhead source of pseudo-random numbers. But the consequence of the "low overhead" implementation is that the number stream has properties that are a long way off perfect ... from a statistical perspective. You have encountered one of the imperfections. Random is documented as being a Linear Congruential generator, and the properties of such generators are well known.

各种处理方式。例如,如果你小心,你可以隐藏一些最明显的差特征。 (但是你会被建议进行一些统计测试。你不能看到第二张图像中添加了噪音的非随机性,但它仍然存在。)

There are a variety of ways of dealing with this. For example, if you are careful you can hide some of the most obvious "poor" characteristics. (But you would be advised to run some statistical tests. You can't see non-randomness in the noise added to your second image, but it could still be there.)

或者,如果您想要保证良好统计属性的伪随机数,那么您应该使用 SecureRandom 而不是 随机 即可。它具有明显更高的开销,但您可以放心,许多聪明人将花费大量时间进行算法的设计,测试和分析。

Alternatively, if you want pseudo-random numbers that have guaranteed good statistical properties, then you should be using SecureRandom instead of Random. It has significantly higher overheads, but you can be assured that many "smart people" will have spent a lot of time on the design, testing and analysis of the algorithms.

最后,创建一个使用替代算法生成数字的 Random 的子类相对简单;请参阅链接。问题是您必须选择(或设计)并实施适当的算法。

Finally, it is relatively simple to create a subclass of Random that uses an alternative algorithm for generating the numbers; see link. The problem is that you have to select (or design) and implement an appropriate algorithm.

将此称为问题是值得商榷的。这是LCG的众所周知和理解的属性,并且LCG的使用是一种有意义的工程选择。人们想要低开销的PRNG,但低开销的PRNG具有较差的属性。 TANSTAAFL。

Calling this an "issue" is debatable. It is a well known and understood property of LCGs, and use of LCGs was a concious engineering choice. People want low overhead PRNGs, but low overhead PRNGs have poor properties. TANSTAAFL.

当然,这不是Oracle在随机中考虑改变的。实际上,中明确说明了不改变的原因。 随机类的javadoc

Certainly, this is not something that Oracle would contemplate changing in Random. Indeed, the reasons for not changing are stated clearly in the javadoc for the Random class.


为了保证此属性,为类 Random 指定了特定的算法.Java实现必须使用此处显示的所有算法 Random ,为了Java代码的绝对可移植性。

"In order to guarantee this property, particular algorithms are specified for the class Random. Java implementations must use all the algorithms shown here for the class Random, for the sake of absolute portability of Java code."

这篇关于Java中setSeed之后的第一个随机数总是相似的的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆