【发布时间】:2013-09-22 05:40:21
【问题描述】:
我记得在面向数学的网站上的一篇文章中读到了一种有效使用随机位的方法,但我似乎无法在 Google 中找到正确的关键字来找到它,而且它不在我的浏览器历史记录中。
所问问题的要点是在域 [domainStart, domainEnd) 中获取一个随机数序列,并有效地使用随机数序列的位均匀地投影到范围 [@ 987654323@,rangeEnd)。域和范围都是整数(更准确地说,longs 而不是 Z)。 执行此操作的算法是什么?
在实现方面,我有一个带有这个签名的函数:
long doRead(InputStream in, long rangeStart, long rangeEnd);
in 基于我需要使用的 CSPRNG(由硬件 RNG 提供,通过 SecureRandom 调节);返回值必须在rangeStart 和rangeEnd 之间,但明显的实现是浪费:
long doRead(InputStream in, long rangeStart, long rangeEnd) {
long retVal = 0;
long range = rangeEnd - rangeStart;
// Fill until we get to range
for (int i = 0; (1 << (8 * i)) < range; i++) {
int in = 0;
do {
in = in.read();
// but be sure we don't exceed range
} while(retVal + (in << (8 * i)) >= range);
retVal += in << (8 * i);
}
return retVal + rangeStart;
}
我相信这实际上与(rand() * (max - min)) + min 的想法相同,只是我们丢弃了将我们推向max 的位。我们没有使用可能会错误地将结果偏向较低值的模运算符,而是丢弃这些位并重试。由于击中 CSPRNG 可能会触发重新播种(这可能会阻止 InputStream),我想避免浪费随机位。 Henry 指出该代码偏向于 0 和 257; Banthar 在一个例子中进行了演示。
第一次编辑:亨利提醒我求和调用了中心极限定理。我已经修复了上面的代码来解决这个问题。
第二次编辑:机械蜗牛建议我查看 Random.nextInt() 的源代码。看了一会,才发现这个问题和base转换问题差不多。请参阅下面的答案。
【问题讨论】:
-
您的“明显实现”不仅浪费而且在概念上也是错误的(除了一些实现错误)。通过添加随机数,您可以更改分布。如果添加了足够的数字,它将变为高斯。例如,掷两个骰子产生 7 的频率要比 2 高得多。
-
谢谢。我知道我在算法上做了一些非常错误的事情。 :S 我应该可以睡一觉了。
-
看
java.util.Random.nextInt的实现。 -
@Banthar 该问题已在此期间进行了编辑;-) 现在可以了。
-
由于最后一个字节的选择方式,还是有一点偏差。