mt_rand() 在 PHP7 中仍存在模偏差问题,因其整数范围截断导致取模后概率不均,正确解法是使用 PHP7+ 的 random_int() 或拒绝采样法。

mt_rand() 在 PHP7 中仍存在模偏差(modulo bias)问题,这不是 PHP7 特有,而是所有基于整数范围截断的随机函数共有的底层缺陷。它会导致某些结果出现概率略高,破坏真正的均匀分布——尤其在抽奖、Token 生成、权限分配等对公平性敏感的场景中不可忽视。
什么是模偏差?
当你用 mt_rand(0, $max) 生成随机数后,再对某个数 n 取模(如 % n),比如:
$random = mt_rand(0, 99) % 10; // 期望 0–9 均匀分布
表面看是 0–9 各占 10%,但 mt_rand(0, 99) 实际返回 100 个值(0 到 99),而 100 % 10 == 0,此时无偏差;
但如果写成:
$random = mt_rand(0, 99) % 7; // 100 ÷ 7 = 14 余 2
那么余数 0 和 1 各被多覆盖一次(0–6、7–13、…、91–97 → 共 14 组,剩下 98、99 映射到 0、1),导致 0 和 1 的概率是 15/100 = 15%,其余是 14% ——这就是模偏差。
正确解决方式(不依赖 mt_rand)
✅ 推荐:用 random_int()(PHP7.0+,CSPRNG 安全)
它是 PHP7 引入的密码学安全伪随机数生成器(CSPRNG),无模偏差,且无需手动防偏:
立即学习“PHP免费学习笔记(深入)”;
// 安全、均匀、无偏差:生成 [1, 100] 内的整数 $number = random_int(1, 100); // 生成 6 位验证码(000000–999999) $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
✅
random_int()内部自动处理边界和均匀性,已规避模偏差,是官方推荐替代方案。
✅ 手动消除偏差(仅当必须用 mt_rand 时)
若因兼容旧代码无法立刻替换,可用「拒绝采样(rejection sampling)」法:
function secure_rand_range($min, $max) {
$range = $max - $min + 1;
$bits = (int) ceil(log($range, 2));
$byteMax = (1 << $bits) - 1;
do {
$val = mt_rand(0, $byteMax);
} while ($val >= $range);
return $min + $val;
}
// 使用示例:生成 [1, 13] 均匀整数(13 不是 2 的幂,易出偏)
$draw = secure_rand_range(1, 13);⚠️ 注意:此方法效率略低(最坏情况循环多次),但能保证数学上严格均匀。
❌ 不推荐:mt_rand() % n 或 (mt_rand() / mt_getrandmax()) * n
-
mt_rand() % n:直接引入模偏差; - 浮点缩放再取整(如
floor(mt_rand() / mt_getrandmax() * $n)):因浮点精度和舍入误差,仍可能分布不均,且mt_getrandmax()在不同平台值不同(如 32 位系统为 2³¹−1),加剧不可控性。
补充提醒
-
mt_rand()不是加密安全的,PHP 手册明确警告:Caution: This function does not generate cryptographically secure values...
即使没模偏差,也不该用于 Token、密码盐、会话 ID 等场景。 - 如果你正在做抽奖、发优惠码、选中奖用户,请务必使用
random_int(); -
openssl_random_pseudo_bytes()或random_bytes()也可用,但需额外转换(如unpack('V', random_bytes(4))[1] % $n仍需防偏),不如random_int()直接。
不复杂但容易忽略:模偏差不会报错,也不会明显“出bug”,但它悄悄让概率失衡——而公平性一旦受损,用户信任就难以重建。



















