PHP浮点数精度丢失的根本原因是数据库字段类型、PDO参数绑定方式及ORM隐式转换策略共同作用,而非PHP本身;Laravel需显式声明$casts为decimal,Yii2需在beforeSave中round(),跨框架应全程保持字符串形态并用DECIMAL字段。

PHP浮点数保存时精度丢失的根本原因
PHP本身不直接决定小数保存精度,真正起作用的是底层数据库字段类型、PDO绑定参数方式,以及ORM层对float/double的隐式转换策略。Laravel和Yii在模型赋值、save()前的数据预处理逻辑不同,导致同一段代码在两个框架中写入MySQL后出现1.234变1.2339999999999999或直接截断为1.23的现象。
Laravel中decimal字段必须显式cast
Laravel默认把数据库DECIMAL(10,2)字段当字符串读取,但若模型没声明$casts,写入时可能被当成float经PHP内部二进制浮点运算再转回字符串,中间就丢了精度。尤其在使用updateOrCreate()或批量upsert()时更隐蔽。
- 必须在模型中加:
protected $casts = ['price' => 'decimal:2']; - 避免用
floatval()或(float)手动转——这会立刻触发精度损失 - 从表单接收数据时,用
filter_var($input, FILTER_SANITIZE_NUMBER_FLOAT)比floatval()更安全
Yii2中attributeLabels不解决存储精度,要靠rules()和beforeSave
Yii2默认对DECIMAL字段不做自动类型映射,price属性始终是string或float,取决于你赋值时给的是什么。如果前端传"1.234",而模型规则没限制,它可能被存成1.2340000000000001。
- 在
rules()里加:['price', 'number', 'min' => 0, 'max' => 999999.99]仅校验,不修正精度 - 真正生效要靠
beforeSave():$this->price = round($this->price, 2);(注意:必须用round(),不是number_format()) - 若用
ActiveRecord::updateAll()绕过模型生命周期,精度控制完全失效——这种写法在Yii里很常见,也是跨框架表现差异的高发场景
跨框架统一方案:数据库层强制约束 + 应用层只传字符串
最稳妥的做法不是依赖框架的cast或rule,而是让小数从进入应用那一刻就保持字符串形态,直到PDO执行前一刻才交给数据库驱动处理。MySQL的DECIMAL能精确存字符串输入,但无法修复PHP浮点计算产生的脏值。
立即学习“PHP免费学习笔记(深入)”;
- 所有金额/权重类字段,前端提交一律用
input type="text"并禁用step="any",后端接收后不做任何float转换 - 数据库字段必须是
DECIMAL(M,D),禁止用FLOAT/DOUBLE - Laravel用
DB::statement()原生查询时,参数绑定要用PDO::PARAM_STR而非默认的PDO::PARAM_STR自动推断(PDO有时会误判为float) - Yii2中
createCommand()->bindValue(':price', $price, PDO::PARAM_STR)比bindValue(':price', $price)更可靠
框架只是工具链一环,真正决定小数能不能存准的,是你有没有在数据离开用户输入框之后、触达数据库之前,彻底切断浮点数参与运算的机会。这点在Laravel和Yii里都容易被忽略——因为它们文档里写的都是“支持decimal”,没写清楚“支持”的前提是开发者不主动把它变成float”。











