ThinkPHP金额计算需全程字符串处理:数据库用DECIMAL,PDO开启ATTR_STRINGIFY_FETCHES,用户输入用input('price/s'),BCMath运算传字符串字面量,写入前用number_format或bcadd格式化。

ThinkPHP里浮点数计算出错,核心不是框架问题,而是PHP底层浮点表示限制被放大了——尤其在金额场景下,0.29存成0.28999999999999998这类失真值,一旦进入运算链就无法挽回。
数据库字段必须用DECIMAL,且读取要确保是字符串
MySQL的FLOAT/DOUBLE类型和PHP float一样不可靠,必须用DECIMAL(10,2)这类定点类型。但光设对字段还不够:PDO默认关闭字符串化(PDO::ATTR_STRINGIFY_FETCHES = false),查出来仍是float。正确做法是显式开启字符串化,或手动转字符串:
• 在数据库连接配置中加 'options' => [PDO::ATTR_STRINGIFY_FETCHES => true]
• 或模型中统一处理:bcadd((string)$model->price, '0', 2)(仅临时补救,不推荐)
用户输入必须强制取字符串,别碰/f过滤器
ThinkPHP的input()方法默认会做类型转换,input('price/f')会把"19.99"转成float,瞬间失真;input('price/s')才是安全选择。
• 表单提交"19.99" → input('price/s') → 字符串"19.99"
• 表单提交"14,99"(欧洲格式)→ 先用 str_replace(',', '.', input('price/s')) 标准化
• 所有进bcadd/bcsub的值,必须带引号字面量或经此路径生成的字符串
BCMath函数调用必须全程字符串,scale参数不能依赖bcscale()
bcscale(2)只影响未显式传$scale的调用,比如bcadd('1.23', '4.56')会按2位算,但bcadd('1.234', '4.567', 2)完全忽略它。更关键的是:传入值本身若已是失真float转来的字符串(如'0.28999999999999998'),bcadd再准也无用。
• 正确示例:bcadd('19.99', '0.01', 2) → '20.00'
• 错误示例:$a = 19.99; bcadd($a, '0.01', 2) → 实际算的是'19.989999999999998' + '0.01'
• 除法bcdiv必须显式写scale,否则默认返回整数部分:bcdiv('100', '3', 2) → '33.33'
立即学习“PHP免费学习笔记(深入)”;
模型写入前必须校验并格式化,不能直接赋float值
哪怕数据库是DECIMAL,$model->amount = 0.29; $model->save()也会触发隐式转换:PHP先把0.29当float处理,再交给PDO,最终入库的可能是0.28999999999999998。
• 安全写法:$model->amount = number_format(input('amount/s'), 2, '.', '')
• 或用BCMath兜底:$model->amount = bcadd(input('amount/s'), '0', 2)
• 配合验证规则:'amount|require|number|regex:/^\d+(\.\d{1,2})?$/',从源头拦截非法格式



















