bcadd、bcsub等bcmath函数在php 5.6和7.1中行为一致,核心陷阱是默认scale=0导致小数丢失,所有运算必须显式指定scale,比较必须用bccomp并传精度,输入须严格字符串校验。

bcadd、bcsub 等函数在 PHP 7.1 和 5.6 行为一致,但默认精度陷阱更隐蔽
PHP 7.1 和 5.6 的 bcadd、bcsub、bcmul、bcdiv 函数本身没有版本级 bug,真正出问题的是「默认 $scale 值」和「隐式字符串转换」。比如:bcadd('1.23', '4.56') 在两个版本都返回 '5'(不是 '5.79'),因为默认 $scale = 0。开发者常误以为“传了小数就自动保留”,结果金额加总后直接丢掉小数位。
- PHP 5.6 和 7.1 都遵循同一套 BCMath 规则:不显式传
$scale,就按bcscale()或bcmath.scaleINI 设置走,默认是0 - PHP 7.1 没修复“精度丢失”,它只是让错误更难察觉——比如
bcmul('1.23', '100')返回'123'(而非'123.00'),而开发者可能没做类型校验就入库,导致后续DECIMAL(10,2)字段报错截断 - PHP 5.6 中若用
float直接传参(如bcadd(0.1, 0.2)),PHP 会先转成字符串,但0.1可能变成'0.10000000000000000555',BCMath 不识别科学计数法,直接报ValueError
bcdiv 必须显式指定 $scale,否则 PHP 5.6/7.1 都返回整数结果
bcdiv 是唯一一个在未传 $scale 时**强制舍去全部小数位**的函数,哪怕你传的是 '10.00' 和 '3.00',bcdiv('10.00', '3.00') 在 PHP 5.6 和 7.1 下都返回 '3',不是 '3.33'。这不是 bug,是设计如此——它的 $scale 参数不可省略(即使设为 null,也会 fallback 到全局 bcscale() 值,而该值默认是 0)。
- 正确写法:
bcdiv('10.00', '3.00', 2)→'3.33' - 错误写法:
bcdiv('10.00', '3.00')→'3'(无论 PHP 版本) - PHP 7.3+ 虽支持
$mode参数控制舍入方式,但 5.6 / 7.1 不支持,只能靠$scale+ 四舍五入逻辑补足(例如先bcdiv($a, $b, 3),再用bcround()自行实现,或改用sprintf('%.2f', round(...))配合字符串校验)
bccomp 比较必须带 $scale,否则 0.29999999999999999 和 0.3 会被判为不等
用 == 或 === 直接比较 BCMath 返回的字符串数字?绝对不行。bccomp 才是唯一安全的比较方式,但它对 $scale 极其敏感。例如:bccomp('0.29999999999999999', '0.3') 返回 1(前者大),但 bccomp('0.29999999999999999', '0.3', 2) 返回 0(相等)。PHP 5.6 和 7.1 行为完全一致,区别只在于:7.1 对无效字符串输入抛 ValueError 更严格,5.6 可能静默失败或返回异常值。
- 所有金融判断(如“是否到账”“是否满减”)必须用
bccomp($a, $b, 2) === 0,不能用$a == $b - 传
$scale = null时,它会读bcscale(),如果之前没调过,就是0,此时bccomp('1.99', '2.00')返回-1(因为只比整数部分) - 注意负零:
bccomp('-0.00', '0.00', 2)返回-1,不是0;实际业务中建议统一用bcadd($x, '0', 2)归一化符号
PHP 5.6 升级到 7.1 后最易踩的兼容性坑
升级本身不会触发 BCMath 报错,但会让原有隐患暴露得更快。比如 PHP 5.6 中一段靠运气跑通的代码:bcadd(0.1 + 0.2, '0.01'),在 5.6 可能因浮点转字符串时机“凑巧”得到 '0.31',但在 7.1 中更大概率转成 '0.30000000000000004',然后 bcadd 直接拒绝该字符串(ValueError)。
- 所有输入必须是格式良好的字符串:
/^[+-]?[0-9]*(\.[0-9]*)?$/,禁止科学计数法、本地化小数点(如逗号)、空格、前导零过多(如'0001.00'建议先ltrim($s, '0')处理) - INI 配置
bcmath.scale=2在 5.6 和 7.1 都有效,但 7.1 更倾向抛异常而不是容忍非法输入 - 不要依赖
bcadd('1', '2')返回'3.00'—— 它永远只返回'3',除非你传$scale=2;后续存 MySQLDECIMAL(10,2)时,必须自己补零或用sprintf('%01.2f', (float)bcadd(...))(仅当确认无精度风险时)
$_POST['amount'] 这种原始值,它可能是 '12.3'、'12.300'、12.3(float)、甚至 '12,3'(欧洲格式),这些全得在进 bcadd 前归一化,否则任一环节松动,整个金融计算就不可信。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











