必须从源头堵住大整数精度丢失:PDO需开启ATTR_STRINGIFY_FETCHES,json_decode须用JSON_BIGINT_AS_STRING,运算用GMP/BCMath,API字段约定ID为字符串。

数据库返回的长整型(比如雪花ID、订单号、区块链地址)一旦超过 PHP_INT_MAX(64位下为 9223372036854775807),PHP 就会静默截断或转成浮点,后续任何 (int) 或 intval() 都已不可逆地丢精度——这不是“能不能修”,而是“必须从源头堵住”。
json_decode 时大整数变 float 或溢出
这是最常见也最容易被忽略的漏点:MySQL 的 BIGINT UNSIGNED 或 PostgreSQL 的 BIGSERIAL 返回的数字,经 json_encode() → HTTP 响应 → json_decode() 后,直接变成 float 或错误 int。
- 现象:
json_decode('{"id":"18446744073709551615"}')得到["id" => 1.8446744073709552E+19],再(int)$data['id']就是9223372036854775807 - 原因:PHP 默认把 JSON 数字当 float 解析,而 float 在 JS/PHP 中最多精确表示
2^53 - 1(约 16 位十进制) - 解法:强制所有数字字段走字符串路径 —— 必须加
JSON_BIGINT_AS_STRING标志:json_decode($json, true, 512, JSON_BIGINT_AS_STRING)
- 注意:Laravel 的
request()->json()、Slim 的$request->getParsedBody()默认不带这个标志,得手动 wrap 或改中间件
PDO/Fetch 时 bigint 自动转 int 导致截断
即使没走 JSON,PDO 直接 fetch 数据也可能把数据库里的超大整数“好心”转成 PHP int,结果就是静默回绕。
- 现象:
$stmt->fetch(PDO::FETCH_ASSOC)返回['id' => 9223372036854775807],但原始数据库值是9223372036854775808 - 原因:PDO 默认启用
PDO::ATTR_STRINGIFY_FETCHES是 false,且底层驱动(尤其 MySQL)倾向用 C long 转换 - 解法:连接时显式开启字符串化:
$pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_STRINGIFY_FETCHES => true, ]); - 备选:查询时用
CAST(id AS CHAR)或CONCAT('', id)强制返回字符串(MySQL);PostgreSQL 用id::text
从字符串安全进入计算环节(GMP/BCMath)
拿到字符串形式的大数只是第一步,后续若需加减乘除等运算,不能直接 + 或 intval(),否则又掉坑里。
立即学习“PHP免费学习笔记(深入)”;
- 别用
(int)"12345678901234567890"—— 结果恒为PHP_INT_MAX或PHP_INT_MIN - 校验是否纯数字字符串(含负号):
is_string($s) && ctype_digit(ltrim($s, '-'))
- GMP 更快更全,但要求扩展存在:
extension_loaded('gmp') && gmp_init($s),之后用gmp_add()、gmp_strval()出结果 - BCMath 内置率高,适合财务类场景:
bcadd($a, $b, 0)
,记得提前设精度bcscale(0),且所有输入必须是字符串 - 除零风险:
bcdiv()报 warning,gmp_div_qr()直接 fatal error,务必前置判零
对外 API 输出时别让前端再溢出一次
后端用字符串存、用 GMP 算完,最后 json_encode() 一输出,前端 JS 又可能把字符串数字自动转 number,再次突破 Number.MAX_SAFE_INTEGER(2^53-1)。
- 现象:PHP 返回
{"id": "12345678901234567890"},但前端JSON.parse()后obj.id变成12345678901234567000 - 根本解法:API 字段设计上就约定「所有 ID 类字段必须为字符串」,并在 OpenAPI spec / 文档中明确标注
type: string - 技术兜底:用
JSON_NUMERIC_CHECK会把字符串数字又转回 number,绝对禁用;确保响应体里全是引号包着的字符串 - 验证方式:用
json_last_error()检查编码是否异常,用正则/:"\d+"/扫描响应体确认数字字段是否带引号
真正麻烦的不是“怎么算大数”,而是整个链路里有太多默认行为在悄悄把字符串转成 int 或 float——从 PDO、json_decode、算术运算符,到最终输出。每个环节都得主动关掉自动转换,而不是指望某处 catch 一个溢出异常。



















