PHP中避免浮点误差需全程使用字符串存储小数并配合bcmath运算:数据库用DECIMAL,PHP层传参和读取均保持字符串,计算时调用bcadd等函数,禁用float参与任何中间环节。

PHP 中保存小数避免浮点误差,不能靠 float 或 double 类型硬扛——它们本质是二进制近似表示,0.1 + 0.2 !== 0.3 是必然结果,不是 bug,是 IEEE 754 的固有行为。
用 string 存储小数(数据库/序列化场景)
当小数用于金额、配置、日志等需要精确呈现的场合,最直接有效的方式是「不把它当数字存」:
- 数据库字段用
DECIMAL(10,2)或NUMERIC,PHP 层统一用字符串传参(PDO 默认不会自动转成 float) - 从数据库读出后,保持为
string;若需计算,再进bcmath,别先(float)$val -
json_encode()时,float会丢失精度,但json_encode(['price' => '19.99'])安全 - 注意:某些 ORM(如 Laravel Eloquent)可能默认把 DECIMAL 字段 cast 成
float,需显式设$casts = ['price' => 'string']
用 bcmath 做算术运算(加减乘除)
bcmath 是 PHP 内置扩展,所有函数都要求操作数为字符串,内部按十进制字符串模拟运算,无浮点误差:
- 必须用字符串入参:
bcadd('0.1', '0.2', 1)→'0.3';写成bcadd(0.1, 0.2, 1)会先被 PHP 转成近似 float,已失真 - 第三个参数
scale控制小数位数,不是四舍五入,而是截断(bcdiv('1', '3', 2)→'0.33') - 乘法
bcmul()不接受scale参数,结果精度由输入字符串决定;如需控制,得手动bcround()(需自定义) - 启用前确认扩展已加载:
extension_loaded('bcmath'),Docker 或 Alpine 环境常需额外安装php-bcmath
别踩 round() 和 number_format() 的坑
这两个函数不解决存储精度问题,只影响显示或临时取整:
立即学习“PHP免费学习笔记(深入)”;
-
round(0.1 + 0.2, 1)→0.3是错觉,因为0.1 + 0.2已是0.30000000000000004,round()只是对这个错误值四舍五入 -
number_format(0.1 + 0.2, 1)同样基于错误 float 值格式化,输出看似对,但底层仍是错的 - 真正需要的是:从源头(输入、存储、中间计算)全程用字符串 +
bcmath,而非最后“修显示”
关键不是选哪种“类型”,而是切断 float 进入业务流程的路径——哪怕只是临时变量,只要经过 +、-、json_decode()(没设 JSON_BIGINT_AS_STRING)、或 MySQL fetch 的自动转换,就可能掉进坑里。











