我记得在一篇关于数学导向网站的文章中读到有效使用随机位的方法,但我似乎无法在Google中找到合适的关键字来找到它,而且它不在我的浏览器历史记录中.
被问到的问题的要点是在域[domainStart,domainEnd]中采用一系列随机数,并有效地使用随机数序列的位来均匀地投射到范围[rangeStart,rangeEnd]中.域和范围都是整数(更准确地说,长而不是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的想法相同,只是我们丢弃了推动我们超过最大值的位.我们丢弃这些位并重试,而不是使用可能错误地将结果偏向较低值的模运算符.由于点击CSPRNG可能会触发重新播种(可以阻止InputStream),我想避免浪费随机位.亨利指出这个代码偏向于0和257;班塔尔在一个例子中证明了这一点.
第一次编辑:亨利提醒我,求和会调用中心极限定理.我修复了上面的代码来解决这个问题.
第二次编辑:Mechanical蜗牛建议我查看Random.nextInt()的源代码.读了一会儿后,我意识到这个问题类似于基本转换问题.见下面的答案.
最佳答案 您的算法产生有偏差的结果.我们假设rangeStart = 0和rangeEnd = 257.如果第一个字节大于0,那将是结果.如果为0,则结果为0或256,概率为50/50.因此,0和256的选择可能性是其他任何数字的两倍.
我做了一个简单的test确认这个:
p(0)=0.001945
p(1)=0.003827
p(2)=0.003818
...
p(254)=0.003941
p(255)=0.003817
p(256)=0.001955
我认为你需要像java.util.Random.nextInt一样做并丢弃整个数字,而不是最后一个字节.