我查看了@ 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
在所需范围内给出随机值的值。如果我们只使用r
when r < n
then(很简单),我们就有了我们想要的均匀分布。如果我们只使用r
whenr < (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**63
r
r
0..m-1
现在(以及对 的所有进一步调用)我们希望从中random_drmath()
均匀地提取随机值,如上所述。所以——步骤(3)——构造小于或等于的最大倍数。如果我们不能使用它,因为其中的值少于-- 这是通常的“拒绝”标准。0..n-1
r
nq
n
m
r >= nq
n
nq..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
。这最后一步感觉很神奇,但我们知道r
innq..m-1
并且每个可能的值具有相等的概率,所以r-nq
is 在范围内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
,单位价值的成本上升。