我有以下代码:
public class Main {
private static final Random rnd = new Random();
private static int getRand(int n) {
return (Math.abs(rnd.nextInt())%n);
}
public static void main(String[] args) {
int count=0, n = 2 * (Integer.MAX_VALUE/3);
for(int i=0; i<1000000; i++) {
if(getRand(n) < n/2) {
count++;
}
}
System.out.print(count);
}
}
这总是给我一个接近666,666的数字。这意味着产生的数字的三分之二低于n的下半部分。并非当n = 2/3 * Integer.MAX_VALUE时获得此值。 4/7是另一个给我类似差异的分数(~5714285)。但是,如果n = Integer.MAX_VALUE或者n = Integer.MAX_VALUE / 2,则得到均匀分布。这种行为与使用的分数有何不同。有人可以对此有所了解。
PS:我从Joshua Bloch的书“Effective Java”中得到了这个问题。答案 0 :(得分:4)
问题在于模数(%)运算符导致数字分布不均。
例如,假设MAX_INT为10,并且n = 7,mod运算符将分别将值8,9和10映射到1,2和3。这将导致数字1,2和3的概率是所有其他数字的两倍。
解决此问题的一种方法是检查rnd.nextInt()
的输出,并在它大于N时重试。
答案 1 :(得分:2)
如果只保留Math.abs(rnd.nextInt())的值在[0..2 / 3(Integer.MAX_VALUE)]的范围内,则会得到50-50。对于剩余的1/3 * Integer.MAX_VALUE数字,由于模数,您将得到[0..1 / 3 Integer.MAX_VALUE]范围内的较小数字。
总而言之,[0..1 / 3 Integer.MAX_VALUE]范围内的数字有两倍的出现机会。
答案 2 :(得分:1)
Random
类旨在生成伪随机数。这意味着它们是具有均匀分布的已定义序列的元素。如果您不知道序列,它们似乎是随机的。
话虽如此,问题在于你通过使用模数运算符搞乱了均匀分布。关于编码恐怖,有一个很好的article解释了这个问题,虽然问题略有不同。现在,您可以找到问题的解决方案以及证明here。
答案 3 :(得分:0)
如上所述,getRand 不在[0,n]范围内生成均匀分布的随机数。
通常,假设n = a * Integer.MAX_VALUE / b,其中a / b> 0.5
为便于写作,设M = Integer.MAX_VALUE
getRand(n)的概率密度函数(PDF)由下式给出:
PDF(x)= 2 / M为0&lt; x&lt; (B-A)M / B
= 1/M for (b-a)M/b < x < aM/b
n / 2对应于[0,aM / b] = aM / 2b
范围的中点将PDF整合到上半年&#39;范围[0,n / 2]我们发现getRand(n)小于n / 2的概率(P)由下式给出:
P = a / b
示例:
a = 2,b = 3。 P = 2/3 = 2/3 = 0.66666 ......由提问者计算。
a = 4,b = 7。 P = 4/7 = 0.5714 ...接近提问者的计算结果。