应强制类型转换后再判断,如if ((int) $n === 1),避免===漏判浮点数1.0/0.0,也规避==的字符串/布尔隐式转换风险,兼顾安全与健壮性。

终止条件用===判断整数会漏掉浮点数输入
PHP里写if ($n === 1)或if ($n === 0)看似严谨,但传入1.0或0.0时直接跳过判断,导致递归继续——这不是bug,是类型严格匹配的必然结果。实际业务中,用户表单、API参数、数据库读取都可能带小数点,尤其float类型在JSON解析后很常见。
- 用
==代替===能解决类型宽松匹配,但有风险:比如"1"、true也会被当作1通过 - 更稳妥的做法是先强制转整型再判断:
if ((int) $n ,或用<code>is_int($n) && $n 双重校验 - 对阶乘这类数学函数,直接拦截非整数:
if (!is_int($n) || $n ,然后<code>return 1或抛出InvalidArgumentException
is_numeric()不能替代类型检查
有人用if (is_numeric($n) && $n 想兜底所有数字类型,但<code>is_numeric("1e2")返回true,而1e2是100,明显不满足终止条件。更糟的是is_numeric("0.0")为true,但0.0 成立,阶乘函数却会错算成<code>0.0 * factorial(-1.0)——负数浮点递归根本停不住。
-
is_numeric()只管“能不能转成数”,不管“是不是你要的数”,别把它当类型过滤器 - 需要数值范围控制时,优先用
filter_var($n, FILTER_VALIDATE_INT),它能拒绝1.0、"1.5"等非整字面量 - 若必须支持浮点输入(如某些统计场景),得明确定义终止逻辑:比如
round($n) ,但要清楚<code>round(0.6)是1,round(0.4)是0
递归调用前不做参数归一化,深层调用会放大类型问题
假设你写了return $n * factorial($n - 1),输入3.0时第一层$n是float,减1后变成2.0,再下一层是1.0,再下一层是0.0……最终卡在0.0 永远成立,但<code>0.0 * factorial(-1.0)持续下去,直到Maximum function nesting level报错。
- 每层递归前做一次类型清理:
$n = (int) $n;或$n = floor($n);(后者对负数更安全) - 不要依赖“上层传下来一定是整数”——PHP函数参数没类型声明时,什么都能进来
- 调试时加一行
var_dump(gettype($n), $n);在递归入口,比猜强十倍
生产环境必须加深度防护,不能只靠终止条件
即使终止条件写对了,恶意构造的超深嵌套(比如传10000)或意外循环引用(比如数组里自己嵌自己)仍会让factorial()跑满256层然后崩。Xdebug默认xdebug.max_nesting_level=256,但这是最后防线,不是设计依据。
立即学习“PHP免费学习笔记(深入)”;
- 在函数签名里加深度计数参数:
function factorial($n, $depth = 0),然后if ($depth > 100) { throw new RuntimeException('Recursion too deep'); } - 用
debug_backtrace(COUNT_NORMAL)实时查当前调用栈长度,比维护计数器更可靠(但有性能开销) - 关键服务里,直接用迭代替代递归——
for ($i = $n; $i > 1; $i--) { $result *= $i; }没栈溢出风险,也更快
类型判断和深度控制得一起上,光修终止条件只是把崩溃从第257层推迟到第258层。



















