我想生成满足0 <= result <= maxValue
的统一整数。
我已经有了一个生成器,它在内置的无符号整数类型的整个范围内返回统一值。我们来调用byte Byte()
,ushort UInt16()
,uint UInt32()
和ulong UInt64()
的方法。假设这些方法的结果完全统一。
我想要的方法的签名是uint UniformUInt(uint maxValue)
和ulong UniformUInt(ulong maxValue)
。
我在寻找:
maxValue
。还可以在某些成员变量中缓存前一次调用中的一些遗留随机性。
小心int溢出和包装行为。
我已经有了一个解决方案(我会将其作为答案发布),但这对我的口味来说有点难看。所以我想获得更好解决方案的想法。
关于如何使用大maxValue
进行单元测试的建议也不错,因为我无法生成具有2 ^ 64个桶和2 ^ 74个随机值的直方图。另一个复杂因素是,对于某些错误,只有一些maxValue
分布偏差很大,而其他分布只有很小的偏差。
答案 0 :(得分:2)
像这样的通用解决方案怎么样?该算法基于Java's nextInt
method使用的算法,拒绝任何会导致非均匀分布的值。只要你的UInt32
方法的输出完全一致,那么这也应该是。
uint UniformUInt(uint inclusiveMaxValue)
{
unchecked
{
uint exclusiveMaxValue = inclusiveMaxValue + 1;
// if exclusiveMaxValue is a power of two then we can just use a mask
// also handles the edge case where inclusiveMaxValue is uint.MaxValue
if ((exclusiveMaxValue & (~exclusiveMaxValue + 1)) == exclusiveMaxValue)
return UInt32() & inclusiveMaxValue;
uint bits, val;
do
{
bits = UInt32();
val = bits % exclusiveMaxValue;
// if (bits - val + inclusiveMaxValue) overflows then val has been
// taken from an incomplete chunk at the end of the range of bits
// in that case we reject it and loop again
} while (bits - val + inclusiveMaxValue < inclusiveMaxValue);
return val;
}
}
理论上,拒绝过程可以永远循环;在实践中,表现应该是相当不错的。如果不知道(a)预期使用模式,并且(b)基础RNG的性能特征,则很难建议任何普遍适用的优化。
例如,如果大多数呼叫者将指定最大值&lt; = 255,则每次要求四个字节的随机性可能没有意义。另一方面,总是检查实际需要的额外成本可能会超过请求更少字节的性能优势。 (当然,一旦你做有特定的信息,那么你可以继续优化和测试,直到你的结果足够好。)
答案 1 :(得分:1)
我不确定,他的答案是肯定的。它绝对需要比评论更多的空间,所以我必须在这里写,但如果其他人认为这是愚蠢的话,我愿意删除。
从我得到的OQ,
我的想法是将二进制数字用于一半,quater ... maxValue空格,直到它减少到一个数字。像
这样的东西我使用maxValue = 333(十进制)作为示例并假设函数getBit()
,随机返回0或1
offset:=0
space:=maxValue
while (space>0)
//Right-shift the value, keeping the rightmost bit this should be
//efficient on x86 and x64, if coded in real code, not pseudocode
remains:=space & 1
part:=floor(space/2)
space:=part
//In the 333 example, part is now 166, but 2*166=332 If we were to simply chose one
//half of the space, we would be heavily biased towards the upper half, so in case
//we have a remains, we consume a bit of entropy to decide which half is bigger
if (remains)
if(getBit())
part++;
//Now we decide which half to chose, consuming a bit of entropy
if (getBit())
offset+=part;
//Exit condition: The remeinind number space=0 is guaranteed to be met
//In the 333 example, offset will be 0, 166 or 167, remaining space will be 166
}
randomResult:=offset
getBit()
可以来自你的熵源,如果它是基于位的,或者是在第一次调用时一次消耗n位熵(显然n是你的熵源的最佳值),并且直到空无一人。
答案 2 :(得分:0)
我目前的解决方案。我的口味有点难看。每个生成的数字也有两个分区,这可能会对性能产生负面影响(我还没有对这部分进行过分析)。
uint UniformUInt(uint maxResult)
{
uint rand;
uint count = maxResult + 1;
if (maxResult < 0x100)
{
uint usefulCount = (0x100 / count) * count;
do
{
rand = Byte();
} while (rand >= usefulCount);
return rand % count;
}
else if (maxResult < 0x10000)
{
uint usefulCount = (0x10000 / count) * count;
do
{
rand = UInt16();
} while (rand >= usefulCount);
return rand % count;
}
else if (maxResult != uint.MaxValue)
{
uint usefulCount = (uint.MaxValue / count) * count;//reduces upper bound by 1, to avoid long division
do
{
rand = UInt32();
} while (rand >= usefulCount);
return rand % count;
}
else
{
return UInt32();
}
}
ulong UniformUInt(ulong maxResult)
{
if (maxResult < 0x100000000)
return InternalUniformUInt((uint)maxResult);
else if (maxResult < ulong.MaxValue)
{
ulong rand;
ulong count = maxResult + 1;
ulong usefulCount = (ulong.MaxValue / count) * count;//reduces upper bound by 1, since ulong can't represent any more
do
{
rand = UInt64();
} while (rand >= usefulCount);
return rand % count;
}
else
return UInt64();
}