Java 按位高效均匀随机数生成
我记得在一个以数学为导向的网站上的一篇文章中读到过一种有效使用随机位的方法,但我似乎再也找不到合适的谷歌关键词了,而且它也不在我的浏览器历史记录中 所问问题的要点是在域[Java 按位高效均匀随机数生成,java,random,entropy,Java,Random,Entropy,我记得在一个以数学为导向的网站上的一篇文章中读到过一种有效使用随机位的方法,但我似乎再也找不到合适的谷歌关键词了,而且它也不在我的浏览器历史记录中 所问问题的要点是在域[domainStart,domainEnd)中获取一个随机数序列,并有效地使用随机数序列的位均匀地投影到范围[rangeStart,rangeEnd)。域和范围都是整数(更准确地说,longs而不是Z)。有什么算法可以做到这一点? 就实现而言,我有一个具有此签名的函数: long doRead(InputStream in, l
domainStart
,domainEnd
)中获取一个随机数序列,并有效地使用随机数序列的位均匀地投影到范围[rangeStart
,rangeEnd
)。域和范围都是整数(更准确地说,long
s而不是Z)。有什么算法可以做到这一点?
就实现而言,我有一个具有此签名的函数:
long doRead(InputStream in, long rangeStart, long rangeEnd);
中的基于我需要使用的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;
}
长数据读取(输入流输入、长范围开始、长范围结束){
长回程=0;
远程=范围结束-范围开始;
//加油,直到我们到达射程
对于(int i=0;(1您的算法会产生有偏差的结果。让我们假设rangeStart=0
和rangeEnd=257
。如果第一个字节大于0
,这将是结果。如果是0
,结果将是0
或256
,概率为50/50
。因此0
和256
are被选中的可能性是其他任何数字的两倍
我做了一个简单的实验来证实这一点:
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相同的操作,并丢弃整数,而只是最后一个字节。将源代码读取到Random.nextInt()后,我意识到这个问题类似于基转换问题
与一次转换单个符号相比,通过累加器“缓冲区”一次转换输入符号块更为有效,累加器“缓冲区”的大小足以表示域和范围中的至少一个符号。新代码如下所示:
public int[] fromStream(InputStream input, int length, int rangeLow, int rangeHigh) throws IOException {
int[] outputBuffer = new int[length];
// buffer is initially 0, so there is only 1 possible state it can be in
int numStates = 1;
long buffer = 0;
int alphaLength = rangeLow - rangeHigh;
// Fill outputBuffer from 0 to length
for (int i = 0; i < length; i++) {
// Until buffer has sufficient data filled in from input to emit one symbol in the output alphabet, fill buffer.
fill:
while(numStates < alphaLength) {
// Shift buffer by 8 (*256) to mix in new data (of 8 bits)
buffer = buffer << 8 | input.read();
// Multiply by 256, as that's the number of states that we have possibly introduced
numStates = numStates << 8;
}
// spits out least significant symbol in alphaLength
outputBuffer[i] = (int) (rangeLow + (buffer % alphaLength));
// We have consumed the least significant portion of the input.
buffer = buffer / alphaLength;
// Track the number of states we've introduced into buffer
numStates = numStates / alphaLength;
}
return outputBuffer;
}
public int[]fromStream(InputStream input,int length,int rangeLow,int rangeHigh)抛出IOException{
int[]outputBuffer=新的int[length];
//缓冲区最初为0,因此它只能处于1种可能的状态
int numStates=1;
长缓冲区=0;
int alphaLength=量程下限-量程上限;
//将outputBuffer从0填充到长度
for(int i=0;i buffer=buffer您的“显而易见的实现”不仅是浪费,而且在概念上也是错误的(除了一些实现错误)。通过添加随机数,您可以更改分布。如果添加的数字足够多,它将变为高斯分布。例如,掷两个骰子将比掷2个骰子产生7的频率高得多。谢谢。我知道我在算法上犯了严重错误。:S我可能应该睡一觉。请查看java.util.random.nextInt @Banthar同时编辑了这个问题;-)现在可以了。由于最后一个字节的选择方式,仍然存在一个小偏差。正确,为了减少超出范围的情况,可以只取必要的位而不是完整的字节。例如,获取[0..700]中的数字只要取10位,而不是两个字节,如果>=700,就放弃。我开始意识到,有一些条件使得这个问题无法解决。我将在稍后编辑这个答案以注意这一点。