我记得在面向数学的网站上的一篇文章中阅读了一种有效使用随机位的方法,但我似乎无法在 Google 中找到正确的关键字来找到它,而且它不在我的浏览器历史记录中。
所问问题的要点是在域 [ domainStart
, domainEnd
) 中获取随机数序列,并有效地使用随机数序列的位均匀地投影到范围 [ rangeStart
, rangeEnd
) 中。域和范围都是整数(更准确地说,long
是 s 而不是 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;
}
我相信这实际上是相同的想法 Henry 指出该代码偏向于 0 和 257;班塔尔在一个例子中演示了它。(rand() * (max - min)) + min
,只是我们丢弃了推动我们前进的部分max
。我们没有使用可能会错误地将结果偏向较低值的模运算符,而是丢弃这些位并重试。由于击中 CSPRNG 可能会触发重新播种(这可能会阻止 InputStream),我想避免浪费随机位。
第一次编辑:亨利提醒我求和调用了中心极限定理。我已经修复了上面的代码来解决这个问题。
第二次编辑:机械蜗牛建议我查看 Random.nextInt() 的源代码。看了一会,才发现这个问题和基数转换问题差不多。请参阅下面的答案。