不能直接用 gmp_fact() 或 bcmath 因生产环境常禁用或未开启,且面试要求手写;递归易栈溢出、需 is_int() 和 ≥0 校验输入合法性。

为什么不能直接用 gmp_fact() 或 bcmath?
因为很多生产环境禁用了 gmp 扩展,bcmath 虽然支持大数但默认不开启;更关键的是——面试或算法题明确要求「不依赖扩展、手写实现」。这时候你得靠纯 PHP 逻辑撑住,而且得扛住边界情况。
递归写法看似简洁,但实际踩坑最多
递归最直观,但 PHP 默认栈深度有限(通常 100 层左右),factorial(200) 就会触发 Fatal error: Maximum function nesting level。另外,没加类型校验时传入负数或非整数,会无限递归或返回错误结果。
- 必须用
is_int()+$n >= 0双重判断输入合法性 - 递归终止条件只能是
$n ,不能只写$n == 0(否则factorial(1)会多调一次) - 别信“尾递归优化”——PHP 不支持尾递归自动优化,
return factorial($n-1) * $n和普通递归一样吃栈
function factorial($n) {
if (!is_int($n) || $n < 0) {
throw new InvalidArgumentException('n must be non-negative integer');
}
if ($n <= 1) return 1;
return $n * factorial($n - 1);
}循环实现才是生产环境首选
迭代写法无栈溢出风险,性能稳定,还能自然兼容大数处理逻辑(比如后续接 bcadd() / bcmul())。但要注意初始值设为 1,不是 0;循环变量从 2 开始,避免乘以 1 的冗余操作。
for ($i = 2; $i 比for ($i = 1; ...)少一次乘法- 如果要支持超大阶乘(如
1000!),需提前检测是否启用了bcmath,并改用bcadd()和bcmul(),且初始值改为'1'字符串 - 整型溢出在 64 位系统上约出现在
factorial(21)之后(21! > PHP_INT_MAX),所以哪怕只是算25!,也建议统一用字符串路径防错
function factorial($n) {
if (!is_int($n) || $n < 0) {
throw new InvalidArgumentException('n must be non-negative integer');
}
if ($n <= 1) return 1;
$result = 1;
for ($i = 2; $i <= $n; $i++) {
$result *= $i;
}
return $result;}
立即学习“PHP免费学习笔记(深入)”;
大数阶乘必须切换到字符串计算
一旦 $n > 20,原生整型就不可靠。此时不能硬刚,得切到 bcmath。但注意:bcmul() 第二个参数必须是字符串,传整型会静默截断;而且 bcadd('0', '1') 这类写法虽合法,但纯属冗余,直接用 '1' 初始化即可。
- 别在循环里反复调用
bcadd('0', $x)做类型转换——开销大且没必要 - 用
extension_loaded('bcmath')检测,而不是function_exists('bcmul')(后者可能被 disable_functions 拦截) - 如果环境真没
bcmath,又必须算大阶乘,只能自己实现字符串乘法,但那是另一层复杂度了
真正容易被忽略的点:很多人写了大数版本,却忘了把输入 $n 也转成字符串再参与 bcmul——bcmul($result, $i) 中的 $i 是整型,PHP 会尝试隐式转换,但在某些配置下会警告或失败。稳妥写法是 bcmul($result, (string)$i)。











