断尾法生成指定范围随机数的进阶思考
- 再创世纪·代码厨房
- 2025-07-26
- 257热度
- 0评论
最近接触了一些随机数的使用场景,对于生成指定范围随机数有了一些新的体会。那么借此机会,直接写成系列文章好了。本文涉及大量数学公式推演,正好学习一下LaTex。后续考虑在使用场景、算法方面再做一些深入研究。
0. “断尾法”之重生
前序文章提到,“断尾法”生成指定范围随机数,其随机性是可靠的,但存在一定概率需要重新生成。极端情况下,重新生成的概率接近
,由此造成其性能可能不佳,转而讨论了“走马灯法”。“走马灯法”可以视为“断尾法”的一种改良,通过“数据循环”的方式利用了本应被舍弃的“尾部”,降低了理论最大性能开销,却也引入了对随机性的一些不利影响。那么换种思路,如果能将“断尾法”中,原始随机数落入“尾部”而导致舍弃需要重新生成的概率降低到可接受的范围内,也是一种权衡的策略。
设伪随机数生成器的宽度为
,则其随机值域为
。“断尾法”中,设尾长度为
,舍弃概率为
,则:
(1) ![]()
希望
尽可能小,只有两种办法,
尽可能小,或者
尽可能大。“走马灯法”可以视为使
尽可能小的一种特殊方法。那么,有什么办法使
显著变大,而不影响整体随机性能?
常见的映射过程是直接将伪随机数发生器的生成值作为输入,使用伪随机数发生器的值域作为随机值域。如果在映射之前,可以将随机值域进行适当拓宽,那么落入尾而被舍弃的概率有可能减小。比如说,对于一个宽度小于
的目标值域,使用64位随机数来映射。这当然可以,只是每次都需要额外增加一倍的资源来生成随机数,资源占用情况与原始“断尾法”的极端情况相当,得不偿失。因此,需要选择一个合适的拓展尺度,以获取尽可能好的“性价比”(性能资源比)。
0. 定义与术语
定义1:本文采用包含0的自然数集定义,即![]()
定义2:自然数的求模运算(mod):如果
,
且
,使
成立,则
。
1. 预备定理及证明
定理1:对于
,![]()
证明:根据定义,必然
且
,使
成立,则
。
![Rendered by QuickLaTeX.com \[ \left. \begin{matrix} k \geq 0, n > 0 &\Rightarrow kn \geq 0\\ r = 0 &\Rightarrow kn + t = 0 \\ &t \geq 0 \end{matrix} \right\}\Rightarrow kn = t = 0 \Rightarrow 0 = 0 \bmod n \]](https://www.xiamengy.net/wp-content/ql-cache/quicklatex.com-7fc10f7bfb6e2b2d462ef88940158a9c_l3.png)
证毕。
定理2:对于
,如果
,则![]()
证明:根据定义,必然
且
,使
成立,则
。
![Rendered by QuickLaTeX.com \[ \left. \begin{matrix} \left. \begin{matrix} r = kn + t < n \Rightarrow (1 - k)n > t \geq 0 \Rightarrow &(1 - k)n > 0\\ &n > 0 \end{matrix} \right\} \Rightarrow &k < 1 \\ &k \geq 0 \end{matrix} \right\} \Rightarrow k = 0 \Rightarrow r = t \Rightarrow r = r \bmod n \]](https://www.xiamengy.net/wp-content/ql-cache/quicklatex.com-0182dfdc916e12211cb350ef4c08427f_l3.png)
证毕。
定理3:对于
,
,![]()
证明:根据定义,设
,则
,且必然
且
,使
成立,则
。
![Rendered by QuickLaTeX.com \[ \left. \begin{matrix} \left. \begin{matrix} R = kn = Kn + T \Rightarrow &0 \leq T = (k - K)n < n\\ &n > 0 \end{matrix} \right\} \Rightarrow &0 \leq k - K < 1 \\ &k, K \in \mathbb{N} \end{matrix} \right\} \Rightarrow \begin{cases}k = K \\ T = 0 \end{cases} \Rightarrow (kn) \bmod n = 0 \]](https://www.xiamengy.net/wp-content/ql-cache/quicklatex.com-29375d02d3a12944afe5069e24b5c207_l3.png)
证毕。
定理4:对于
,
,![]()
证明:根据定义,必然
且
,使
成立。不妨设
,
,则
。
(2) ![]()
于是
,
且
,使
成立,满足mod运算定义
则![]()
证毕。
定理5:对于
,
,![]()
证明:设
(其中
且
),必然
,使
。则
![]()
根据定理3、4可知,
(3) ![]()
证毕。
定理6:对于
,
,![]()
证明:设
(其中
,
,且
),必然
,使
。则
![]()
根据定理3、4可知,
(4) ![]()
证毕。
2. 拓宽随机值域与舍弃概率的定量关系
设伪随机数生成器的宽度为
,目标值域宽度为
,尾长为
,且
使
成立。
当
时,
,在随机数映射的场景无实际意义;
当
时,
(5) ![]()
当且仅当
时,
(6) ![]()
加下标0表示
取给定值时对应的各个参数,则
(7) ![]()
将随机值域拓宽为
倍 (
),加下标m表示将
拓宽之后的各个参数(此时保持
不变),则
(8) ![]()
当
时,
![]()
此种情况下,随机宽度增大,舍弃概率
却没有减小,徒增资源投入,应避免出现。
当
时,
![]()
此种情况下,舍弃概率
减小至0,是理论上的最佳。
当
时,可对(8)式继续展开
(9) 
由于
,则必然
使
,此时
(10) ![]()
可见,当
增大时,舍弃概率
以指数速率减小。综上所述,当
,即
时,舍弃概率
必然减小。
对
与
的定量关系继续推理,
![]()
不等式两边同取
为底的对数,
![]()
又
,
,则
![Rendered by QuickLaTeX.com \[ i - 1 < \frac{\log_{A}{\dfrac{n_0}{mR}}}{\log_{A}{p_0}} \leq i \]](https://www.xiamengy.net/wp-content/ql-cache/quicklatex.com-fe2755a1fab6557b3233f68287c13ff6_l3.png)
考虑到
,则
(11) 
3. 拓宽随机值域与资源开销的定量关系
设某伪随机数生成流程的舍弃概率为
,
表示生成符合要求随机数(首次不被舍弃)所需要的次数,数列
表示与次数
对应的概率,则
(12) 
一个稳定的伪随机数发生器,其每一次生成随机数所需的资源开销(比如时间复杂度、空间复杂度)应当是一致的,多次生成所需的总开销与生成次数成正比。设该伪随机数发生器每一次生成随机数需要的资源为常数
,生成一次符合要求的随机数(首次不被舍弃)的期望资源开销为
,则
(13) 
如果使用多次生成/裁切的方法来拓宽随机值域,则生成所需的总开销与总位数成正比。此处需要特别注意,随机数的总位数并非其最大值(例如32位伪随机数发生器产生的每一个随机数在二进制下位数皆为32,而其最大值为
)。有一宽度为
的伪随机数发生器,在
进制下的位数为
,则
;另有一变量
,在
进制下位数为
,则
。此处不限制
。将该伪随机数发生器值域扩展至
倍,此时随机值域的位数为
,则
(14) ![]()
对其值域拓展至
倍,对应所需资源为
,则
![]()
由此可得
(15) ![]()
加下标0表示未拓宽随机值域的各个参数,则
![]()
加下标m表示拓宽随机值域后的各个参数,则
![]()
当
,所需资源开销减少,则
(16) 
对不等式两边取 的指数,则
(17) ![]()
综上所述,当
时,所需资源开销必然减小。
4. 拓宽因子
如果一个随机数发生器是符合均匀分布的,这就表明该发生器生成的任一随机数的出现概率均等。对于32位伪随机数发生器,均匀分布又等价于:该发生器生成的任一32 位随机数 ,其每一位取0或者1的概率皆为
。一个 32 位无符号整形数R可以表示为:
(18) 
其中,
是第
位的比特值(0 或 1)。从此32位随机数中任取
位,其每一位取0或者1的概率仍为
,则该
位组成
中任一值的概率皆为
,符合均匀分布。特别的,当
时,即取出了一个字节,该字节的值在
也是均匀分布的。
由前述推理可知,当
时,舍弃概率必然减小;当
时,所需资源开销必然减少。考虑到常见的伪随机数发生器的宽度一般为
,再考虑现代计算机常见的内存结构是以字节为单位的(长度小于字节的内存结构需要通过位操作完成),则在必要的时候拓宽8位(即取一个完整字节)是合理的最小值。以下取
试算32位伪随机数发生器的极端情况下,舍弃概率与所需资源开销的实际变化。由于
与
都用二进制表示,此处取
。
对于32位伪随机数发生器,
(19) 
当
时,
(20) 
由此可知,当
时,对于极端情况,舍弃概率由约
显著减小至约
,所需资源开销由约
减小至约
,优化的效果非常可观,且当
时,期望资源开销收敛在
附近,其它变量的影响可以忽略不计。因此,对于一个给定的
值,不妨加下标1表示各个变量,则当
时,拓宽操作才有意义,即
(21) 
即当原始舍弃概率大于
时,使用
对随机值域进行拓宽,期望资源开销必然减小。由此定义一个符合均匀分布的8位随机数为随机数发生器宽度的拓宽因子。根据前述的分析,使用符合均匀分布的伪随机数发生器生成的32位无符号随机数的每一个字节,可以作为拓宽因子,而不影响随机性能。
5. 代码实现
uint32_t GetRandomUInt32(uint32_t a, uint32_t b)
{
if(a > b)
{
return GetRandomUInt32(b, a);
}
if(a == b)
{
return a;
}
if(0 == a && UINT32_MAX == b)
{
return GetRandomUInt32();
}
uint32_t iRangeSize = b - a + 1;
// 防止溢出,等价于uint32_t iTailSize = (UINT32_MAX + 1) % iRangeSize;
uint32_t iTailSize = (UINT32_MAX - iRangeSize + 1) % iRangeSize;
// 防止溢出,等价于if(iTailSize > (UINT32_MAX + 1) / 5)
bool bExpand = false;
if(iTailSize > (UINT32_MAX - 4) / 5 + 1)
{
iTailSize = (iTailSize << 8) % iRangeSize; // 定理4
bExpand = true;
}
// 拓宽后,舍弃仅发生在0xff == iExpand时,因此仍然记录32位的体长即可
uint32_t iMaxBodyValue = UINT32_MAX - iTailSize;
if(bExpand)
{
while(true)
{
uint8_t iExpand = GetRandomUInt8();
uint32_t iRand = GetRandomUInt32();
if(0xff == iExpand && iRand > iMaxBodyValue)
{
continue;
}
// 防止溢出,等价于a + (iExpand << 32 + iRand) % iRangeSize;
return a + (iExpand * ((UINT32_MAX - iRangeSize + 1) % iRangeSize) + iRand) % iRangeSize;
}
}
else
{
while(true)
{
uint32_t iRand = GetRandomUInt32();
if(iRand > iMaxBodyValue)
{
continue;
}
return a + iRand % iRangeSize;
}
}
}
其中,GetRandomUInt32()是一个均匀分布的32位伪随机数发生器;GetRandomUInt8()是一个8位伪随机数发生函数,该函数截取32位发生器生成的一个32位无符号数的一个字节作为输出。整体代码实现并不复杂。
二零二五年七月二十六日
顾毅写于厦门