有效的Java项目47:了解并使用您的库 - 有缺陷的随机整数方法示例

时间:2015-01-05 12:05:03

标签: java random probability effective-java non-uniform-distribution

在Josh给出的有缺陷的随机方法的例子中,该方法生成一个具有给定上限n的正随机数,我不明白他所陈述的两个缺陷。

书中的方法是:

private static final Random rnd = new Random();

//Common but deeply flawed
static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}
  • 他说如果n是2的小幂,则生成的随机数序列将在短时间后重复。为什么会这样? Random.nextInt()的文档说Returns the next pseudorandom, uniformly distributed int value from this random number generator's sequence.所以不应该是,如果n是一个小整数,那么序列会重复,为什么这只适用于2的幂?
  • 接下来他说,如果n不是2的幂,一些数字平均会比其他数字更频繁地返回。如果Random.nextInt()生成均匀分布的随机整数,为什么会发生这种情况? (他提供了一个代码片段,清楚地证明了这一点,但我不明白为什么会这样,以及这与n是2的权力有什么关系。)

2 个答案:

答案 0 :(得分:36)

  

问题1:如果n是2的小幂,则生成的随机数序列将在短时间后重复出现。

这不是乔希所说的任何事情的必然结果;相反,它只是linear congruential generators的已知属性。维基百科有以下说法:

  

LCG的另一个问题是,如果m被设置为2的幂,则生成序列的低阶位具有比整个序列短得多的周期。通常,n个最低有效位在输出序列的基数b表示中,其中对于某些整数k,b k = m,重复最多为句点b n

Javadoc

中也提到了这一点
  

已知线性同余伪随机数生成器(例如由该类实现的生成器)在其低阶位的值序列中具有短周期。

函数的另一个版本Random.nextInt(int)通过在这种情况下使用不同的位来解决这个问题(强调我的):

  

该算法特别处理n为2的幂的情况:它从底层伪随机数生成器返回正确数量的高阶位。

这是一个很好的理由,希望Random.nextInt(int)优先使用Random.nextInt()并进行自己的范围转换。

  

问题2:接下来他说如果n不是2的幂,那么平均一些数字会比其他数字更频繁地返回。

nextInt()可以返回2个 32 个不同的数字。如果您尝试使用% n将它们放入n个桶中,并且n不是2的幂,则某些桶将具有比其他桶更多的数量。这意味着即使原始分布是统一的,某些结果也会比其他结果更频繁地发生。

让我们用小数字看一下。假设nextInt()返回了四个等概率结果,0,1,2和3.让我们看看如果我们将% 3应用于它们会发生什么:

0 maps to 0
1 maps to 1
2 maps to 2
3 maps to 0

正如您所看到的,算法将返回0的频率是返回1和2中每一个的两倍。

当n是2的幂时,这不会发生,因为一个2的幂可以被另一个整除。考虑n=2

0 maps to 0
1 maps to 1
2 maps to 0
3 maps to 1

这里,0和1以相同的频率出现。

其他资源

以下是一些额外的 - 如果只是切向相关 - 与LCG相关的资源:

答案 1 :(得分:5)

1)当n是2的幂时,rnd % n相当于选择原始的几个低位。由java使用的生成器类型生成的较低位数已知比较高位“较不随机”。它只是用于生成数字的公式的属性。

2)想象一下,random()返回的最大可能值为10,n = 7。现在,n % 7将数字7,8,9和10分别映射为0,1,2,3。因此,如果原始数字是均匀分布的,则结果会严重偏向较低的数字,因为它们的出现次数是4,5和6的两倍。在这种情况下,无论是否n,都会发生这种情况。是2的幂,但是,如果不是10,我们选择15(即2 ^ 4-1),那么任何n,即2的幂将导致均匀分布,因为在范围的末尾不会留下“过剩”数字来引起偏差,因为可能值的总数可以被可能的剩余数量完全整除。