我查看了@ Peter.O指向的“快速骰子滚轮”(FDR),这确实很简单(并且避免了划分)。但是每次生成一个随机数时,这都会消耗一些位并丢弃它不使用的那些位的一部分。
“批处理”/“池化”技术似乎比 FDR 做得更好,因为(至少部分)保留了未使用的比特部分。
但有趣的是,您引用的DrMath与 FDR 基本相同,但它返回的每个随机值都不是从头开始的。
所以返回0..n-1的 FDR是:
random(n):
m = 1 ; r = 0
while 1:
# Have r random and evenly distributed in 0..m-1
# Need m >= n -- can double m and double r adding random bit until
# we get that. r remains evenly distributed in 0..m-1
while m < n: r = 2*r + next_bit() ; m = m*2
# Now have r < m and n <= m < n*2
if r < n: return r # Hurrah !
# Have overshot, so reduce m and r to m MOD n and r MOD m
m -= n ; r -= n ;
DrMath 是这样的:
# Initialisation once before first call of random(m)
ms = 1 ; rs = 0
N = ... # N >= maximum n and N*2 does not overflow
# The function -- using the "static"/"global" ms, rs and N
random(n):
m = ms ; r = rs
while 1:
# Same as FDR -- except work up to N not n
while m < N: r = 2*r + next_bit() ; m = m*2 ;
# Now have r < m and m >= N
# Set nq = largest multiple of n <= m
# In FDR, at this point q = 1 and nq = n
q = m DIV n ;
nq = n * q
if r < nq: # all set if r < nq
# in FDR ms = 1, rs = 0
ms = q # keep stuff not used this time
rs = r DIV n # ditto
return r MOD n # hurrah !
# Overshot, so reduce MOD n*q -- remembering, for FDR q == 1
m = m - nq
r = r - nq
如前所述,它与 FDR 基本相同,但会跟踪未使用的随机性。
测试时我发现:
FDR: for 100000 values range=3 used 266804 bits cost=1.6833
DrMath: for 100000 values range=3 used 158526 bits cost=1.0002
其中注意到 log2(3) = (1.58496) cost。bits-used / (100000 * log2(3))(所以cost是使用的位数除以希望使用的位数)。
还:
FDR: for 100000 values range=17: 576579 bits cost=1.4106
DrMath: for 100000 values range=17: 408774 bits cost=1.0001
和:
FDR: for 100000 values ranges=5..60: 578397 bits cost=1.2102
DrMath: for 100000 values ranges=5..60: 477953 bits cost=1.0001
其中构造了 100000 个值,并为每个值选择了一个范围5..60(含)。
在我看来 DrMath 有它!尽管对于更大的范围,它的优势较小。
请注意... DrMath 每个返回的随机值至少使用 2 个除法,这给了我运行时间方面的建议。但是您确实说过您对运行时效率不感兴趣。
它是如何工作的 ?
因此,我们希望一系列随机值r均匀分布在一个范围内0..n-1。不方便的是,我们只有一个随机源,它为我们提供了均匀分布在 中的随机值0..m-1。通常m是 2 的幂——让我们假设n < m(如果n == m问题是微不足道的,如果n > m问题是不可能的)。对于任何r我们可以r MOD n在所需范围内给出随机值的值。如果我们只使用rwhen r < nthen(很简单),我们就有了我们想要的均匀分布。如果我们只使用rwhenr < (n * q)并且(n * q) < m我们也有一个均匀分布。我们在这里“拒绝” r“太大”的东西。越少r我们拒绝,更好。所以我们应该选择q这样(n * q) <= m < (n * (q-1))——所以n * q是n小于或等于的最大倍数m。反过来,这告诉我们n“少得多”比m是首选。
当我们“拒绝”一个给定的r东西时,我们可以把它全部扔掉,但事实证明这并不是完全必要的。此外,m不一定是 2 的幂。但我们稍后会讨论。
这是一些工作的Python:
M = 1
R = 0
N = (2**63) # N >= maximum range
REJECT_COUNT = 0
def random_drmath(n):
global M, R, REJECT_COUNT
# (1) load m and r "pool"
m = M
r = R
while 1:
# (2) want N <= m < N*2
# have 0 <= r < m, and that remains true.
# also r uniformly distributed in 0..m-1, and that remains true
while m < N:
r = 2*r + next_bit()
m = m*2
# (3) need r < nq where nq = largest multiple of n <= m
q = m // n
nq = n * q
if r < nq:
# (4) update the m and r "pool" and return 0..n-1
M = q
R = r // n
return r % n # hurrah !
# (5) reject: so reduce both m and r by MOD n*q
m = m - nq
r = r - nq
REJECT_COUNT += 1
必须有N>= 最大范围,最好更大。 2**31或者2**63是显而易见的选择。
在第 (2) 步的第一次调用中,random_drmath()将读取随机位以“填充池”。对于,N = 2**63将以63 个随机位结束。显然是随机且均匀分布在 中的。[到现在为止还挺好。]m = 2**63rr0..m-1
现在(以及对 的所有进一步调用)我们希望从中random_drmath()均匀地提取随机值,如上所述。所以——步骤(3)——构造小于或等于的最大倍数。如果我们不能使用它,因为其中的值少于-- 这是通常的“拒绝”标准。0..n-1rnqnmr >= nqnnq..m-1
那么,哪里r < nq可以返回一个值——步骤(4)。这里的诀窍是将m和r视为数字“base-n”。的 ls “数字”r被提取 ( r % n) 并返回。然后m和r右移一位“数字”(q = m // n和r // n),并存储在“池”中。我认为很明显,此时r和m仍然是随机r < m且r均匀分布在0..m-1. 但是m不再是 2 的幂——但这没关系。
但是,如果r >= nq必须减少r并m一起——步骤(5)——再试一次。琐碎,可以设置m = 1 ; r = 0并重新开始。但是我们所做的是nq从两者中减去m和 ,r 这使得r均匀分布在 中0..m-1。这最后一步感觉很神奇,但我们知道rinnq..m-1并且每个可能的值具有相等的概率,所以r-nqis 在范围内0..m-nq-1并且每个可能的值仍然具有相等的概率!while[请记住,循环顶部的“不变量”r是随机且均匀分布在 中的0..m-1。]
对于小n的拒绝步骤将丢弃大部分r,但对于小的n(与 相比N)我们希望不要经常拒绝。相反,对于大n(与 相比N),我们可能希望更频繁地拒绝,但这至少保留了我们迄今为止吃过的一些随机位。我觉得可能有一种方法可以保留更多r……但是还没有想到一种简单的方法来做到这一点……如果读取一个随机位的成本很高,那么可能值得尝试找到一个不简单的方法!
FWIW:设置N = 128我得到:
FDR: for 100000 values ranges=3.. 15: 389026 bits cost=1.2881
DrMath: for 100000 values ranges=3.. 15: 315815 bits cost=1.0457
FDR: for 100000 values ranges 3.. 31: 476428 bits cost=1.2371
DrMath: for 100000 values ranges 3.. 31: 410195 bits cost=1.0651
FDR: for 100000 values ranges 3.. 63: 568687 bits cost=1.2003
DrMath: for 100000 values ranges 3.. 63: 517674 bits cost=1.0927
FDR: for 100000 values ranges 3..127: 664333 bits cost=1.1727
DrMath: for 100000 values ranges 3..127: 639269 bits cost=1.1284
所以随着n接近N,单位价值的成本上升。