PHP json_encode对浮点数默认按14位有效数字截断,非小数位保留;应提前转字符串并用sprintf('%.17g')控制精度,再禁用科学计数法,确保JSON中为字符串而非数字。

PHP json_encode 对浮点数默认四舍五入到14位有效数字
PHP 内置的 json_encode 在处理浮点数时,会按 C 语言底层 double 的精度规则截断,**不是简单保留小数位,而是控制有效数字总长度**。典型表现是:输入 123456789.0123456789,输出变成 123456789.012346(14位有效数字),甚至更小的数如 0.000000123456789 直接转成 1.23456789e-7。
这不是 bug,是 PHP 7.1+ 默认行为 —— 它调用的是 zend_print_double,硬编码了 EG(precision)(默认为 14)作为有效数字上限。
- 该精度值可通过
ini_set('precision', 17)临时提高,但不推荐:过大会导致科学计数法更频繁,且影响全局浮点输出 -
serialize()或var_export()不受此限制,但它们不是 JSON 格式 - 若数据要给 JS 解析,注意 JS 的
Number.MAX_SAFE_INTEGER限制(2^53−1),超精度整数在 JS 里本就会丢失
用 JSON_UNESCAPED_UNICODE + 自定义浮点字符串化绕过
最稳妥的做法不是改全局 precision,而是**提前把关键浮点数转成字符串**,让 json_encode 不走 double 路径。尤其适合金额、坐标、传感器读数等不允许精度扰动的场景。
示例:
立即学习“PHP免费学习笔记(深入)”;
<?php
$data = [
'price' => 123.4567890123456789,
'lat' => 39.90469000000001,
'lng' => 116.40717000000002
];
<p>// 遍历并识别 float/double,转成带足够小数位的字符串
array_walk_recursive($data, function (&$v) {
if (is_float($v) || (is_string($v) && is_numeric($v) && strpos($v, '.') !== false)) {
// 保留17位小数(覆盖 IEEE 754 double 最大可精确表示的小数位)
$v = sprintf('%.17g', $v);
// 再确保不出现 e+、e- 形式(强制禁用科学计数法)
if (strpos($v, 'e') !== false || strpos($v, 'E') !== false) {
$v = sprintf('%.17f', $v);
$v = rtrim(rtrim($v, '0'), '.');
}
}
});</p><p>echo json_encode($data, JSON_UNESCAPED_UNICODE);
// → {"price":"123.45678901234568","lat":"39.90469000000001","lng":"116.40717000000002"}
?></p>-
sprintf('%.17g', $v)优先用紧凑格式(自动省略末尾 0),%.17f强制固定小数位,二者配合可兼顾可读性与精度 - 不要用
number_format($v, 17):它强制补零,可能引入无意义的 0,且对极小数仍会出 e - 如果字段名固定,建议只对明确字段做转换(如
$data['price'] = sprintf('%.17g', $data['price']);),避免递归污染其他数值类型
用 JSON_PARTIAL_OUTPUT_ON_ERROR + 自定义 JSON 编码器(PHP 7.3+)
PHP 7.3 引入了 JSON_PARTIAL_OUTPUT_ON_ERROR,但它本身不解决浮点精度问题;真正有用的是配合 JsonSerializable 接口,把浮点逻辑封装进对象。
适用于已有业务模型类、且需长期维护精度的项目:
<?php
class PreciseFloat implements JsonSerializable {
private $value;
<pre class='brush:php;toolbar:false;'>public function __construct($value) {
$this->value = (float)$value;
}
public function jsonSerialize() {
// 关键:这里控制输出格式
$s = sprintf('%.17g', $this->value);
return strpos($s, 'e') === false ? $s : sprintf('%.17f', $this->value);
}}
$data = [ 'amount' => new PreciseFloat(0.1 + 0.2), // 0.30000000000000004 → "0.3" ]; echo json_encode($data, JSON_UNESCAPED_UNICODE); // → {"amount":"0.3"} ?>
- 必须返回字符串(不是 float),否则
json_encode会再次处理它 - 不能返回数字字面量(如
(float)0.3),否则又回到原问题 - 该方案隔离性强,不影响其他数组或第三方库的数据流
前端接收时也要小心 JS 的 Number 精度陷阱
即使 PHP 输出了完整字符串,一旦前端用 JSON.parse() 接收,再赋值给 JS 变量,如果字段没加引号,JS 仍会转成 Number 类型并丢失精度。
- 后端输出必须是字符串:即
"price": "123.4567890123456789",而不是"price": 123.4567890123456789 - 前端解析后,应直接使用字符串(如传给后端接口、显示),如需计算,先用
BigInt或专用库(如decimal.js)处理 - 检查 Chrome 控制台打印的值是否带引号 —— 如果没引号,说明后端没转字符串,或者前端代码误用了
parseFloat()二次转换
浮点精度问题从来不是单点故障,它横跨序列化、传输、解析、运算四个环节。最容易被忽略的是:你以为修好了 PHP 输出,却没意识到 JS 已经悄悄把它变回了 double。



















