今日神游,忽然沉思:随机是指现实生活中无规律,不可预测的事情,例如:抛硬币。那么在软件的世界里是如何实现这一物理现象的呢,遂做此探究。。。
要弄明白程序是怎么实现随机的,首先得明白两个概念:
伪随机数(Pseudo-Random Number, PRN)是通过确定性算法生成的,看似随机但可预测的数列。这类随机数由数学公式计算得出,如果使用相同的初始种子(seed),则会生成相同的随机数序列。
线性同余生成器(Linear Congruential Generator, LCG)
( X_{n+1} = (aX_n + c) \mod m )
Java 的 Random
类使用 LCG 算法。
梅森旋转算法(Mersenne Twister, MT19937)
Python 的 random
模块使用 MT19937,周期长((2^{19937}-1)),适用于科学计算和游戏随机数。
XOR-Shift 算法
java.util.concurrent.ThreadLocalRandom
采用此算法,适用于多线程环境。
PCG(Permuted Congruential Generator)
近年提出的高质量伪随机数算法,周期长且统计性质优良。
真随机数(True Random Number, TRN)依赖物理现象产生,例如:
硬件随机数生成器(Hardware RNG, HRNG)
RDRAND
指令 RDSEED
指令 操作系统熵池(Entropy Pool)
/dev/random
(熵不足时会阻塞)、 /dev/urandom
(非阻塞) CryptGenRandom
API 用户行为
特性 | 伪随机(Pseudo-Random) | 真随机(True Random) |
---|---|---|
定义 | 计算机算法生成的可预测随机数 | 依赖物理现象生成的不可预测随机数 |
是否可复现 | 是 | 否 |
是否可预测 | 是 | 否 |
生成速度 | 快 | 较慢 |
是否有周期性 | 有 | 无 |
是否适用于密码学 | 否 | 是 |
上述概念我们其实可以得知,真随机一定要依赖现实世界的物理现象,因此,如果是纯软件实现的随机函数,都是伪随机。如果软件代码依赖了硬件(例如:硬件噪声)那么就可以实现真随机。
import java.util.Random;
public class PseudoRandomExample {
public static void main(String[] args) {
Random random = new Random(12345); // 固定种子
System.out.println(random.nextInt(100)); // 生成 0~99 之间的随机数
System.out.println(random.nextInt(100));
System.out.println(random.nextInt(100));
}
}
输出(每次运行相同):
51
35
86
Java 的 java.util.Random
类就是一个典型的伪随机数生成器(PRNG),它使用线性同余生成器(Linear Congruential Generator, LCG)算法。
LCG 公式如下: [ X_{n+1} = (aX_n + c) \mod m ]
Random
类的默认 seed
是 System.nanoTime() ^ multiplier
Random
类的源码解析Random
主要依赖 next()
方法,每次调用 nextInt()
、nextLong()
等方法时,都会基于 LCG 计算新的伪随机数。
private final AtomicLong seed;
private static final long MULTIPLIER = 0x5DEECE66DL;
private static final long ADDEND = 0xBL;
private static final long MASK = (1L << 48) - 1;
MULTIPLIER = 0x5DEECE66DL
(乘数) ADDEND = 0xBL
(增量) MASK = (1L << 48) - 1
(模数,保证种子始终在 48 位范围) next()
方法protected int next(int bits) {
long oldSeed, nextSeed;
AtomicLong seed = this.seed;
do {
oldSeed = seed.get();
nextSeed = (oldSeed * MULTIPLIER + ADDEND) & MASK; // LCG 公式
} while (!seed.compareAndSet(oldSeed, nextSeed));
return (int) (nextSeed >>> (48 - bits)); // 取高 bits 位作为随机数
}
seed
compareAndSet
保证线程安全 48-bits
以返回所需的位数 nextInt()
实现public int nextInt() {
return next(32);
}
nextInt()
直接调用 next(32)
,返回 32 位整数 next()
方法返回 48-bits
,但 nextInt()
只取高 32 位 nextInt(int bound)
(带上界)public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException("bound must be positive");
int r, m = bound - 1;
if ((bound & m) == 0) // 如果 bound 是 2 的幂
return (int) ((bound * (long) next(31)) >> 31);
do {
r = next(31);
} while (r - (r % bound) + m < 0); // 确保均匀分布
return r % bound;
}
bound
是 2 的幂,可以直接用位运算优化 bound
不是 2 的幂,用 拒绝采样法(Rejection Sampling) 处理边界情况,保证均匀分布 import java.security.SecureRandom;
public class TrueRandomExample {
public static void main(String[] args) {
SecureRandom secureRandom = new SecureRandom();
System.out.println(secureRandom.nextInt(100)); // 生成 0~99 之间的真随机数
}
}
SecureRandom
真的是真随机吗?其实,即便使用了SecureRandom
, 也不一定是真随机,它的随机性取决于底层的实现。
SecureRandom
默认是加密安全的伪随机数生成器(CSPRNG)。它通常使用操作系统的安全随机源,如:
/dev/urandom
或 /dev/random
CryptGenRandom
API SecureRandom
使用真随机SecureRandom secureRandom = SecureRandom.getInstanceStrong();
强制使用真随机源:
SecureRandom secureRandom = SecureRandom.getInstance("NativePRNGBlocking");
NativePRNGBlocking
使用 /dev/random
,但可能会阻塞。 NativePRNGNonBlocking
使用 /dev/urandom
,速度快,但可能不是完全真随机。 以上就是今日的一些思考,希望对你有帮助。
欢迎大家关注gzh: 加瓦点灯, 每天推送有意思的知识!
本文由 mdnice 多平台发布