我有一个应用程序,我需要测量算法消耗的随机性位数。我已经检测了Random
的子类,通过覆盖Random.next(int)
来增加计数器,然后再调用其父方法。
我遇到了nextInt(int)
方法实现的一些问题,因为即使范围是2的幂,它总是会绘制32位。对于其他范围,还有更多问题:方法不统一---对于绘制的原始值大于低于{{1的范围的最大倍数的情况,它仅重试一次它仍然使用比所需更多的随机性。
如何实现更好的Integer.MAX_VALUE
版本,它只使用确定范围内的值所需的最小随机位数,同时完全统一?它不需要保证终止(无论如何都不可能),只需以概率1终止。
这是我到目前为止所做的:
nextInt(int)
这可能不完全正确,但实际上这会导致int nextInt(int max){
int n = Integer.numberOfTrailingZeros(max);
return next(n) + nextOddInteger(max >> n) << n;
}
的所有n
两个因素,生成一个num
位的随机数,并在n
前加上nextOddInteger(num)
得到的比特。 nextOddInteger
将生成一个随机整数,其整数分解不包含任何二元组。如何以非常随机的方式实现这部分?
答案 0 :(得分:4)
让我说说随机整数生成算法,就其平均使用的随机位数而言,它们是“最佳”的。在本文的其余部分,我们将假定我们有一个“真实的”随机生成器,该生成器可以生成无偏且独立的随机位。
1976年,DE Knuth和AC Yao表明,任何仅使用随机位以给定概率生成随机整数的算法都可以表示为二叉树,其中随机位指示遍历树和每片叶子的方式(端点)对应于结果。他们还给出了给定算法平均需要多少位才能完成此任务的下限。在这种情况下,用于均匀地在[0, n)
中生成整数的最优算法平均平均最多需要log2(n) + 2
位。在这种意义上,有许多 optimized 算法的示例。其中一个是J. Lumbroso(2013)的Fast Dice Roller(在下面实现),另一个可能是2004年Math Forum中给出的算法。另一方面,所有算法{{3 }}并非最佳选择,因为它们一次只能生成随机位块。
但是,任何最优整数生成器(也都是无偏)通常会在最坏的情况下永远运行,如Knuth和Yao所示。回到二叉树,n个结果标签中的每一个都留在二叉树中,以便[0,n)中的每个整数都可以1 / n的概率出现。但是,如果1 / n具有不间断的二进制扩展(如果n不是2的幂,就会是这种情况),那么该二进制树必然是其中一个-
,无论哪种情况,即使平均使用很少的随机位,该算法也将在最坏的情况下永远运行。 (另一方面,当n为2的幂时,最佳二叉树将具有有限的深度且没有拒绝节点。)快速骰子滚子是使用“拒绝”事件以确保其无偏的算法示例;请参阅下面的代码中的注释。
因此,通常,随机整数生成器可以是 无偏的或恒定时间(甚至没有),但不能同时是两者。特别是,通常没有办法在不引入偏差的情况下“修复”不确定运行时间的最坏情况。例如,模减少(例如randInt() % n
)等效于二叉树,其中拒绝叶子用标记的结果替换-但是由于拒绝叶子的可能性更大,因此只有部分结果可以代替拒绝叶子,造成偏见。如果您在设置一定数量的迭代后停止拒绝,则会产生相同类型的二叉树和相同类型的偏差。 (但是,根据应用的不同,这种偏见可以忽略不计。随机整数生成还存在安全方面的问题,这些问题太复杂了,无法在此答案中讨论。)
请注意,我们假设我们有一个随机位发生器。但是,Java实现中存在一个难题:它无法生成单独的随机位。 (实际上,大多数伪随机数生成器一次生成的是位块,而不是单个位。)因此,如在撰写本文时在另一个答案中提到的那样,为了最佳利用随机位,您需要保存结果nextInt()
的位,并一次读出一位。读取完所有位后,生成另一个nextInt()
并重复。
以下是Fast Dice Roller的JavaScript实现。请注意,它使用拒绝事件和循环来确保它没有偏见。 nextBit()
是一个随机位生成器(例如Math.random()<0.5 ? 1 : 0
,就Java或JavaScript中最终依赖的随机位而言,不一定有效)。
function randomInt(minInclusive, maxExclusive) {
var maxInclusive = (maxExclusive - minInclusive) - 1
var x = 1
var y = 0
while(true) {
x = x * 2
var randomBit = nextBit()
y = y * 2 + randomBit
if(x > maxInclusive) {
if (y <= maxInclusive) { return y + minInclusive }
// Rejection
x = x - maxInclusive - 1
y = y - maxInclusive - 1
}
}
}
答案 1 :(得分:0)
听起来你应该绘制所有的比特,将它们打在持久队列中,然后根据需要弹出这些比特来消耗它们。当你用完时,只绘制新的随机位。
e.g。如果Random.next绘制32位并且你需要8位,那么你只需要为每4个需要8位的调用绘制一次。