php整型溢出不报错而静默回绕,判断真溢出需运算前检查:用字符串长度预估、gmp/bcmath库处理,或从数据库/pdo源头以字符串获取大整数,json编码前将超界整数转字符串。

PHP整型溢出后值变负数或错乱,怎么判断是否真溢出了?
PHP的int类型在32位系统上最大是2147483647(PHP_INT_MAX),64位系统是9223372036854775807。溢出不报错,而是静默回绕——比如PHP_INT_MAX + 1变成PHP_INT_MIN。这不是bug,是C底层行为。
- 直接比较
$n > PHP_INT_MAX没用:溢出后$n已经变小了 - 正确做法是运算前检查:用
gmp_add()或bcmul()前先转字符串,或用filter_var($str, FILTER_VALIDATE_INT)粗筛输入范围 - 更稳妥的是全程用
GMP或BCMath对象,不依赖原生int
示例:判断$a + $b是否安全
if (strlen((string)$a) + strlen((string)$b) > 20) {
// 字符串长度和超20,大概率溢出,走GMP
$result = gmp_strval(gmp_add($a, $b));
} else {
$result = $a + $b; // 小数放心算
}大整数加减乘除,该选GMP还是BCMath?
两者都行,但设计目标不同,选错会踩坑:
-
GMP是C库封装,速度极快,适合密码学、哈希、大质数等场景,但只支持整数(除法会截断)立即学习“PHP免费学习笔记(深入)”;
BCMath专为十进制高精度设计,支持小数位控制(bcscale(2)),适合金额计算,但性能比GMP低3–5倍不要混用:
gmp_init('123')和bcadd('123', '456')结果不能直接比较,类型不兼容GMP函数返回资源或GMP对象,不能直接echo,得用gmp_strval()转字符串BCMath所有参数必须是字符串,传int可能被自动转成科学计数法(如1e18),导致精度丢失
从数据库读出来的超大整数(如bigint)一运算就变0或负数
MySQL的BIGINT UNSIGNED最大到18446744073709551615,远超PHP_INT_MAX(即使64位PHP)。PDO默认把数字字段转成PHP int,一超过就回绕。
- 解决方案不是改PDO设置,而是强制以字符串取值:
PDO::ATTR_STRINGIFY_FETCHES => true(推荐)
或查询时用CAST(id AS CHAR) - Laravel用户注意:
$model->id默认是int,得写$model->getAttributes()['id']或改模型的$casts为'id' => 'string' - 不要用
(string)$id临时转换——如果原始值已溢出回绕,转字符串也没用,必须从源头(fetch阶段)就保真
JSON编码含大整数的数组时,数字被截断成0或null
json_encode()对超出PHP_INT_MAX的整数无能为力,会输出0或触发E_WARNING(取决于json_last_error()),且不可恢复。
- 根本解法:在
json_encode()前遍历数据,把可能越界的数字字段转成字符串 - 简单过滤写法:
array_walk_recursive($data, function (&$v) { if (is_int($v) && ($v > PHP_INT_MAX || $v < PHP_INT_MIN)) { $v = (string)$v; } }); - 更健壮的做法是用
JsonSerializable接口,在类中控制序列化逻辑,避免全局遍历
大整数问题本质是PHP把“整数”和“数值表示”绑得太死。真正难的不是调哪个函数,而是意识到:只要涉及ID、时间戳、金额、哈希值这些可能超限的字段,就得提前决定——它到底是个“可计算的数”,还是个“不可分割的标识符”。后者一律当字符串处理,反而最省事。











