
为什么 0.1 + 0.2 !== 0.3 在 PHP 里也成立
这不是 PHP 的 bug,是所有遵循 IEEE 754 双精度浮点标准的语言共有的表现。PHP 的 float 类型底层用的是 C 的 double,64 位二进制无法精确表示十进制的 0.1、0.2 这类小数,它们在内存里已经是近似值了。
常见错误现象:var_dump(0.1 + 0.2 == 0.3); // bool(false);或者用 == 比较两个看似相等的计算结果,却意外失败。
- 别用
==或===直接比较浮点数是否“相等” - 需要判断是否“足够接近”,用差值绝对值小于某个容差(epsilon)代替
- 容差选多大?一般
1e-10到1e-6足够,具体看业务精度要求(比如金额通常用分整数存储,根本不用浮点)
示例:
function floatEquals($a, $b, $epsilon = 1e-10) {<br> return abs($a - $b) < $epsilon;<br>}<br>var_dump(floatEquals(0.1 + 0.2, 0.3)); // true
用 round() 做“四舍五入”反而更危险
round() 看似能解决显示问题,但它不改变底层精度,还可能放大误差。尤其当参数是表达式时,PHP 会先算出带误差的中间值,再 round——这个中间值本身已经失真。
立即学习“PHP免费学习笔记(深入)”;
使用场景:仅适用于对显示或日志输出做格式化,**不能用于逻辑判断或后续计算**。
-
round(0.1 + 0.2, 1)得到0.3,但这是“碰巧”,不是保证 - 如果写
round(1.005, 2),可能得到1.0而不是1.01(受二进制表示和舍入模式影响) - PHP 8.2+ 引入了
PHP_ROUND_HALF_UP等显式模式,但依然无法绕过底层精度缺陷
更稳妥的做法:涉及精度敏感运算(如财务),全程用整数(单位“分”),或用 bcadd()、bcmul() 等 BCMath 函数。
bcadd() 和 number_format() 别混着用
bcadd() 是任意精度十进制计算函数,输入必须是字符串,否则传入的 float 变量早已失真;number_format() 是纯格式化函数,返回字符串,不能参与计算。
常见错误:把 float 直接塞给 bcadd(),以为能“修复精度”,实际白搭。
- 正确用法:
bcadd('0.1', '0.2', 1)—— 所有数字都用字符串字面量 - 错误用法:
$a = 0.1; $b = 0.2; bcadd($a, $b, 1)——$a和$b此刻已是近似值 -
number_format()只该出现在最终输出环节,比如echo number_format($cents / 100, 2);,但前提是$cents是整数
性能影响:BCMath 比原生 float 运算慢一个数量级,别在高频循环里无脑替换。
JSON 编码时 float 自动转成字符串的坑
PHP 默认 JSON 编码会把 float 转成科学计数法或丢失末尾零(如 1.0 → 1),前端 JS 解析后类型变成 integer,破坏预期结构。
使用场景:API 接口返回数值字段,且前端依赖类型(比如 Vue 响应式依赖属性类型变化)。
- PHP 7.3+ 可用
JSON_PRESERVE_ZERO_FRACTION标志强制保留小数点后零,但只对整数值有效(如1.0→1.0),对0.1无效 - 真正可控的方式:手动转字符串,比如
'price' => sprintf('%.2f', $price),但要注意这又引入了sprintf的浮点误差风险 - 最稳方案:内部统一用整数 cents 存储,JSON 输出前除以 100 并用
number_format()格式化为字符串
容易被忽略的一点:哪怕你用了 BCMath 计算,只要最后赋值给一个 float 变量,它就立刻变回 IEEE 754 表示——精度控制必须贯穿从输入、计算到输出的每个环节,断一环就失效。











