uint32_tarc4random_uniform(uint32_tupper_bound){uint32_tr,min;if(upper_bound<2)return0;/* 2**32 % x == (2**32 - x) % x */min=-upper_bound%upper_bound;/*
* This could theoretically loop forever but each retry has
* p > 0.5 (worst case, usually far better) of selecting a
* number inside the range we need, so it should rarely need
* to re-roll.
*/for(;;){r=arc4random();if(r>=min)break;}returnr%upper_bound;}
即,排除靠近 0 的这一份总数为 min 的导致采样偏差的样本。
上述代码中 min = -upper_bound % upper_bound 利用了 k 为整数、m 为正整数时任意数 x 与 x±km 同余,即 x≡x±km(modm) 的性质,将 232modupper_bound 改为更容易表示的 (232−upper_bound)modupper_bound,即 -upper_bound % upper_bound。
考虑我们有一个可以生成 [0,2L−1] 范围内均匀分布的伪随机数,希望基于此生成 [0,s−1] 范围内的均匀分布的伪随机数,其中 0≤s≤2L 。此处的 s 就是前面算法中的 upper_bound。
新算法具体来说是这样的:
首先,使用 [0,2L−1] 范围内均匀分布的伪随机数来生成一个随机数 x 。
为了便于理解,不妨将这个数 x 除以 2L,这样我们就得到一个在 [0,1) 范围内、间隔为 2L1 的均匀分布的定点小数。具体而言,由于 0≤x≤2L−1,因此 2L0≤2Lx≤2L2L−1。使用一个包含 2L 个比特的的整数,我们就可以将这个定点数表达为其高 L 位作为其整数部分(全0),而其低 L 位作为其小数部分(x)。
定点小数的乘法规则与整数乘法无异。因此,在将长度为 L 的整数 x 扩展到 2L 个比特之后,将 x 乘以 s 我们便可以得到一个整数部分取值范围为 [0,s−1] 的数。从函数角度,这样一来,我们便把所有满足 0≤x×s<2L 的 x 对应的输出都映射到0、所有满足 2L≤x×s<2×2L 的 x 对应的输出都映射到1,或更一般地,将所有满足 i×2L≤x×s<(i+1)×2L 的 x 值所对应的输出都映射到 i。
与原算法类似,这样一来依然会出现一些采样偏差,我们需要消除这些偏差。
下文中,将小于或等于 x 的最大整数记作 ⌊x⌋。将大于或等于 x 的最小整数记作 ⌈x⌉。将 x 除以 y 的整数部分即 ⌊x/y⌋ 记作 x÷y。
观察不难发现,对于整数 a,b,s,对于满足 b>a>0 并且 s>0 的情形,若 b−a 能被 s 整除,则在 [a,b) 范围内必然存在 (b−a)÷s 个 s 的倍数。
令 a=i×2L+(2Lmods), b=(i+1)×2L,因此 b−a=2L−(2Lmods),故 b−a 一定能被 s 整除,因而在 [a,b) 即 [i×2L+(2Lmods),(i+1)×2L) 范围内,存在且仅存在 2L÷s 个 s 的倍数。
由于我们的目标是在每一个 i×2L≤x×s<(i+1)×2L 范围内可能的 x 取值映射到输出 i 上,这个结论告诉我们,只要我们排除了所有位于 [i×2L,i×2L+(2Lmods)) 范围内的 x×s 值,便可以获得一致的样本了。注意到此处大量出现的 2L,我们恰好可以把这一判断写作检测该乘积的小数部分上,因为我们可以通过判断 x×smod2L<2Lmods 来得到同样的结论。
/*-
* SPDX-License-Identifier: 0BSD
*
* Copyright (c) Robert Clausecker <fuz@FreeBSD.org>
* Based on a publication by Daniel Lemire.
* Public domain where applicable.
*
* Daniel Lemire, "Fast Random Integer Generation in an Interval",
* Association for Computing Machinery, ACM Trans. Model. Comput. Simul.,
* no. 1, vol. 29, pp. 1--12, New York, NY, USA, January 2019.
*/#include<stdint.h>#include<stdlib.h>uint32_tarc4random_uniform(uint32_tupper_bound){uint64_tproduct;/*
* The paper uses these variable names:
*
* L -- log2(UINT32_MAX+1)
* s -- upper_bound
* x -- arc4random() return value
* m -- product
* l -- (uint32_t)product
* t -- threshold
*/if(upper_bound<=1)return(0);product=upper_bound*(uint64_t)arc4random();if((uint32_t)product<upper_bound){uint32_tthreshold;/* threshold = (2**32 - upper_bound) % upper_bound */threshold=-upper_bound%upper_bound;while((uint32_t)product<threshold)product=upper_bound*(uint64_t)arc4random();}return(product>>32);}