java.util.Random有多好?

47

两个问题:

每次输入不同的种子,我会得到不同的数字序列吗?

是否存在一些“失效”的种子?(产生零或非常快地重复。)

顺便问一下,除了Mersenne Twister,我还可以使用其他 PRNG 吗?

解决方案:由于我将使用 PRNG 制作游戏,因此不需要它是加密安全的。为了速度和极长周期,我选择使用 Mersenne Twister。

6个回答

52
在某种程度上,随机数生成器是因地制宜的。Random类实现了一个具有合理选择参数的LCG。但是它仍然表现出以下特点:
- 相当短的周期(2^48) - 比特不是等概率随机的(请参见我的比特位置的随机性文章) - 只会生成值的一小部分组合(“落入平面”的著名问题)
如果这些事情对你不重要,那么Random有一个补救措施,即作为JDK的一部分提供。它足够好用于休闲游戏(但不适用于涉及金钱的游戏)。没有弱种子。
另一个选择是XORShift生成器,可以在Java中实现如下:
public long randomLong() {
  x ^= (x << 21);
  x ^= (x >>> 35);
  x ^= (x << 4);
  return x;
}

对于一些非常便宜的操作,这个生成器的周期为2^64-1(不允许为零),足够简单,可以在重复生成值时进行内联。有多种移位值可用:详见George Marsaglia的XORShift生成器论文以获取更多细节。您可以将生成的数字中的位视为等同随机。一个主要的弱点是偶尔会陷入“困境”,其中数字中没有设置很多位,然后需要经过几代才能走出这个困境。
其他可能性包括:
- 组合不同的生成器(例如将XORShift生成器的输出馈送到LCG中,然后将结果加到具有不同参数的XORShift生成器的输出中):这通常允许不同方法的弱点被“平滑化”,并且如果仔细选择组合的生成器的周期,则可以获得更长的周期。 - 添加“滞后”(以获得更长的周期):本质上,当一个生成器通常会转换最后一个生成的数字时,存储一个“历史缓冲区”,并转换,例如第(n-1023)个。
我建议避免使用需要大量内存才能提供比您实际需要的周期更长的生成器(有些生成器的周期大于宇宙中的原子数量-您通常不需要那么多)。请注意,“长周期”不一定意味着“高质量生成器”(尽管2^48仍然有点低!)。

15
不,实际上是2的48次方!不要相信博客中所写的一切——看看java.lang.Random的实际源代码吧!注意这一行:seed = (seed * multiplier + 0xbL) & ((1L << 48) - 1); - Neil Coffey
4
没错,你说得对,我已经纠正了。末尾漏掉了口罩这个细节。不管怎样,那里仍然有有用的信息 - 它并不明显如何得到周期种子 = (种子 * 倍增器 + 增量),而且底层的数学计算方式也是出人意料的。附言:这是我的博客,我倾向于相信我所写的内容 :) - Dimitris Andreou
2
将多个生成器组合起来实际上是一种很好的方法,可以在重复值之前得到一个意外地小的循环。 - Rentsy
2
是的!D. Knuth,《计算机程序设计艺术》,第2卷:(3.2.2)“与随机数生成相关的常见谬论之一是认为我们可以取一个好的生成器并稍加修改,以获得一个更加随机的序列。这通常是错误的……新序列可能会更不随机。因为整个理论都崩溃了,在没有关于序列行为的任何理论的情况下……这些序列可能比从更有纪律的函数中获得的序列表现得更差。” - Rentsy
Rentsy - 这是真的,但我认为他从引用中指的是对单个生成器的算法/种子进行微小调整,这并不完全相同(当然,这本身就是一个有效的警告)。 - Neil Coffey
显示剩余3条评论

10
正如zvrba所说,该JavaDoc解释了正常的实现方式。 维基百科关于伪随机数生成器页面上有大量信息,并且提到了Mersenne twister,它不被认为是密码学安全的,但速度非常快,而且在Java中有各种实现。(最后一个链接有两个实现 - 我相信还有其他可用的实现。)
如果您需要进行密码学安全生成,请阅读维基百科页面 - 有各种选项可供选择。

6
作为随机数生成器,Sun的实现绝对不是最先进的,但对于大多数目的来说已经足够好了。如果您需要用于加密目的的随机数,可以使用java.security.SecureRandom,如果您只需要比java.util.random更快更好的东西,则很容易在网络上找到Mersenne Twister的Java实现。

喜欢你简短的回答。经过9年,该链接已失效。https://docs.oracle.com/javase/9/docs/api/java/security/SecureRandom.html - akauppi

4

这在文档中有描述。线性同余发生器在理论上已经得到了很好的理解,并且有很多关于它们的材料可以在文献和互联网上找到。具有相同参数的线性同余生成器总是输出相同的周期序列,而种子唯一决定的是序列开始的位置。因此,对于您的第一个问题的答案是:“是的,如果您生成足够多的随机数。”


0

请在我的博客文章中查看答案:

http://code-o-matic.blogspot.com/2010/12/how-long-is-period-of-random-numbers.html

随机数的状态有最大周期(即长达2^64的周期)。这可以直接推广到2^k - 投入尽可能多的状态位,就能获得最大周期。相比之下,Mersenne Twister仅具有非常的周期(请参见上述博客文章中的评论)。

-- 糟糕。随机数实际上只使用了长整型的48个位,而不是全部64个位,因此其周期实际为2 ^ 48,而不是2 ^ 64。


-3
如果您真的很在意随机数生成器(RNG)的质量,我建议您使用自己的RNG。也许java.util.Random在您的操作系统上的这个版本非常好用,但是这可能会改变。以前已经发生过库编写者在后续版本中使事情变得更糟的情况。
编写自己的RNG非常简单,这样您就知道确切的情况。它不会因为升级等原因而改变。这里有一个生成器,您可以在10分钟内将其移植到Java中。如果您从现在开始一周后开始使用某种新语言,您可以再次移植它。
如果您不实现自己的RNG,则可以从可靠来源获取著名RNG的代码并在项目中使用它。然后没有人会在您的生成器下更改它。

(我并不主张人们自己设计算法,只是自己实现。大多数人,包括我在内,没有开发自己算法的能力。编写一个你认为很棒的烂生成器很容易。这就是为什么人们需要像这个问题一样询问,想知道库生成器是多好。我提到的生成器中的算法已经经过了许多同行评审的考验。)


11
我不赞成编写自己的生成器这个想法——这就像编写自己的加密程序一样:除非你真的非常擅长数学且非常小心地实现,否则它很可能会失败。有许多经过充分研究并仔细实现的算法可供选择。 - Jon Skeet
正如您在文章中所指出的那样:“随机数生成是棘手的任务。好的随机数生成算法很难发明。实现算法的代码很难测试。”为什么要重复造轮子呢? - Jon Skeet
我并不是在暗示您不能设计/实现自己的伪随机数生成器。但作为一名研究统计学家,您具有一定的优势 :) - Jon Skeet
2
我认为实现一个随机数生成器并不神秘。算法本身可能有些神秘,但是只要有清晰的描述,我认为普通人也可以正确地编写它们的代码。 - John D. Cook
4
顺便提一下:java.lang.Random的算法不会改变。实际算法和LCG参数是规范的一部分。 - Neil Coffey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接