1.随机数的概念
随机数 是指在某个数值范围内,每个数出现的概率均等且无法被事先预测的数。生成随机数的方法称为随机数生成算法,根据其真实随机性来源,分为两类:
1. 真随机数生成器(TRNG,True Random Number Generator)
-
原理:利用物理过程的不可预测性,例如:
-
电子元件的热噪声(电阻噪声)
-
放射性衰变的时间间隔
-
量子效应(如光子偏振方向)
-
大气噪声(无线电台干扰)
-
-
特点:
-
完全不可重现(无法通过相同的输入得到相同序列)
-
无周期性
-
生成速度较慢,通常需要专用硬件
-
-
应用:密码学密钥生成、彩票开奖、安全令牌等对不可预测性要求极高的场景。
2. 伪随机数生成器(PRNG,Pseudo-Random Number Generator)
-
原理 :采用确定性的数学公式或算法,从一个初始值(种子)开始迭代计算,产生一个长周期、统计上均匀的数列。
-
特点:
-
种子相同 → 生成的随机数序列完全相同(可重现)
-
周期有限(但可以设计得非常长,如 MT19937 周期为 219937−1219937−1)
-
生成速度极快,无需硬件支持
-
-
常见算法:
-
线性同余生成器(LCG) :Xn+1=(a⋅Xn+c)mod mXn+1=(a⋅Xn+c)modm(如 C 语言的
rand()常基于此) -
梅森旋转算法(Mersenne Twister,MT19937):周期长、分布均匀,广泛用于科学计算和游戏
-
Xorshift:基于异或和移位操作,速度极快
-
PCG(Permuted Congruential Generator):LCG 的改进版,统计质量高
-
-
应用:蒙特卡洛模拟、游戏随机事件、程序测试、非加密场景。
3. 密码学安全伪随机数生成器(CSPRNG,Cryptographically Secure PRNG)
-
性质:在 PRNG 基础上增加安全要求:
-
即使攻击者获得连续多个输出,也无法推算出之前或之后的状态(前向/后向不可预测性)
-
种子足够大且不可猜测
-
-
常见实现:
-
ChaCha20(流密码结构)
-
基于哈希的 DRBG (如 HMAC_DRBG,用于 Linux 内核的
/dev/urandom) -
Yarrow(FreeBSD 等系统使用)
-
-
应用:生成会话密钥、随机数用于 TLS/SSL、身份验证挑战值等。
总结表
| 类型 | 随机性来源 | 是否可重现 | 典型算法 | 主要用途 |
|---|---|---|---|---|
| 真随机(TRNG) | 物理现象 | 否 | 热噪声采样、量子RNG | 高安全性、抽奖、密钥生成 |
| 伪随机(PRNG) | 数学公式 | 是(种子相同) | LCG, MT19937, Xorshift | 模拟、游戏、一般编程 |
| 密码学安全伪随机(CSPRNG) | 数学公式+安全约束 | 是(种子保密) | ChaCha20, HMAC_DRBG | 加密协议、安全令牌 |
注意 :日常编程中用
srand(time(NULL)) + rand()生成的是伪随机数,因速度快、可重现便于调试。而对安全性有要求的场景(如重置密码链接的 token)必须使用 CSPRNG。
2.伪随机的应用
1. 什么是"伪随机"?
伪随机数 是指通过确定性的数学算法生成的、看起来像随机数的数列。
-
"伪" 的意思是"假的、模拟的"------它不是真正不可预测的随机数,而是由固定算法计算出来的。
-
但因为数列能通过常见的随机性统计检验(如分布均匀、前后无关联),所以在实际工程中把它当作随机数使用。
真正的随机数(如热噪声、放射性衰变)是无法通过公式重复生成的,而伪随机数本质上是一个周期很长、分布均匀的数列。
2. 只要种子相同,生成的随机数序列就相同吗?
是的,一定相同 。
伪随机数生成器(PRNG)是一个确定性的函数:
-
你给一个初始值(种子 ,比如
srand(seed)中的seed),它就会从该状态出发,按照固定公式产生第一个随机数,然后更新内部状态,再产生第二个,依次类推。 -
如果两次运行的种子相同,整个数列就会完全一致。
例如:
c
srand(123);
printf("%d ", rand()); // 假设输出 41
printf("%d ", rand()); // 假设输出 18467
只要种子是 123,在任何机器、任何时间运行,前两个 rand() 输出总是 41, 18467(具体数值依赖标准库实现,但对同一实现是固定的)。
3. 为什么用时间戳作种子?
为了让每次程序运行时种子不同,从而得到不同的随机数序列。
-
time(NULL)返回当前秒数,每秒变化一次。 -
这样你在不同时刻运行程序,种子不同,序列也就不同,达到"每次运行结果不一样"的直观效果。
但注意:如果程序在同一秒内启动两次,种子相同,两次的随机数序列也会相同。
4. 伪随机 vs 真随机
| 特性 | 伪随机(PRNG) | 真随机(硬件随机数) |
|---|---|---|
| 确定性 | 是(种子决定全部) | 否 |
| 可重复性 | 种子相同则序列可重现 | 不可重现 |
| 周期 | 有限长(但极长,如2^19937-1) | 无周期 |
| 来源 | 数学公式 | 物理现象(热噪声、量子效应) |
| 用途 | 模拟、游戏、非安全场景 | 密码学、抽奖等要求不可预测的场景 |
5. 补充:伪随机并不"假"
虽然它是确定的,但质量高的 PRNG(如梅森旋转算法)生成的数列在统计上与真随机几乎无法区分。
对于绝大多数应用(如游戏、蒙特卡洛模拟),伪随机数完全够用,并且可重现的优点是调试和复现实验结果的好帮手。
3.伪随机的算法(根据时间戳生成指定范围内的随机数)
在 C 语言中,根据时间戳生成指定范围内的随机数通常分两步:
-
用
time(NULL)获取当前时间戳作为种子,调用srand()初始化随机数生成器。 -
用
rand()生成随机数,并通过算术运算限制到[min, max]区间。
下面是完整示例代码:
c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/**
* 生成 [min, max] 范围内的随机整数
* @param min 最小值
* @param min 最大值
* @return 随机整数
*/
int random_in_range(int min, int max) {
if (min > max) {
// 交换,保证参数合法
int temp = min;
min = max;
max = temp;
}
// 方法1:取模运算(适用于范围不大时)
// return rand() % (max - min + 1) + min;
// 方法2:浮点数缩放(分布更均匀,推荐)
return (int)((double)rand() / (RAND_MAX + 1.0) * (max - min + 1) + min);
}
int main() {
// 用当前时间戳(秒)设置随机种子
srand((unsigned int)time(NULL));
// 示例:生成 10 个 [5, 20] 范围内的随机数
int min = 5, max = 20;
for (int i = 0; i < 10; i++) {
int r = random_in_range(min, max);
printf("%d ", r);
}
printf("\n");
return 0;
}
关键点说明:
-
时间戳 :
time(NULL)返回从 1970‑01‑01 至今的秒数,每次运行程序通常不同,保证种子随机性。 -
种子设置 :
srand()只需调用一次,一般在程序开头。 -
范围限制:
-
取模法
rand() % (max - min + 1) + min简单直接,但若max-min+1不能整除RAND_MAX+1,会导致低值略高的偏差(一般可接受)。 -
浮点缩放法更均匀,推荐使用。
-
-
头文件 :
<stdlib.h>提供rand/srand,<time.h>提供time。
注意事项:
-
如果程序在 同一秒内多次运行 (例如循环中反复调用),
time(NULL)返回相同值,导致相同随机序列。此时可改用纳秒级时间(如clock_gettime)或混合其他熵源。 -
多线程环境下,
rand()不是线程安全的,建议使用rand_r或 C11 的rand_s。
如果需要更高质量的随机数(如加密场景),应使用 getrandom(Linux)或 rand_s(Windows)。但对于一般应用,上述方法足够。