你不应该在项目中直接使用加减乘除
在我们的项目中,你不能直接使用加减乘除,而是应该使用 bcmath 这个扩展库。但是这个扩展库提供的接口并不是非常好用,所以你应该使用别人在这个基础上封装好的 brick/math
库。
浮点计算是个难题
在所有的计算机语言中,数值计算都是容易的。比如:
echo 1 + 2; // output: 3
但是,涉及到小数,这就成了一个非常困难的事情。因为计算机采用二进制,注定了无法表示一些小数。比如:
var_dump(0.1 + 0.2); // output: float(0.30000000000000004)
这并不仅仅是 PHP 的问题,你换一种语言,比如 JavaScript 或者 Java 也是一样的。
使用 brick/math
还是 0.1 + 0.2
的例子,使用 brick/math
来计算:
use Brick\Math\BigDecimal;
$result = BigDecimal::of(0.1)->plus(0.2)->toFloat();
var_dump($result); // float(0.3)
加减乘除的示例:
use Brick\Math\BigDecimal;
use Brick\Math\Exception\MathException;
use Brick\Math\RoundingMode;
try {
$num1 = BigDecimal::of(0.1);
$num2 = BigDecimal::of(0.2);
echo $num1->plus($num2)->toFloat().PHP_EOL;
echo $num1->dividedBy($num2, 3, RoundingMode::HALF_EVEN)->toFloat().PHP_EOL; // 0.5
echo $num1->multipliedBy($num2)->toFloat().PHP_EOL; // 0.02
echo $num1->minus($num2)->toFloat().PHP_EOL; // -0.1
} catch (MathException $e) {
echo $e->getMessage().PHP_EOL;
}
RoundingMode
在实际的生产中,我们更倾向于使用银行家算法(RoundingMode::HALF_EVEN)。简单来说就是 “四舍六入五分二” 或 “向最近的偶数取舍”,经常在财务计算中使用。这个算法的好处是它可以减少因四舍五入引起的累计误差,使得数据在大量运算后能更加接近真实值。
下面的表格仅供参考。
模式 | 描述 | 示例 |
---|---|---|
RoundingMode::UP |
总是向上舍入(逼近正无穷大) | 1.3 到 2, -1.3 到 -2 |
RoundingMode::DOWN |
总是向下舍入(逼近负无穷大) | 1.6 到 1, -1.6到 -2 |
RoundingMode::CEILING |
总是向正无穷大方向舍入 | 1.6 到 2, -1.6 到 -1 |
RoundingMode::FLOOR |
总是向负无穷大方向舍入 | 1.6 到 1, -1.6 到 -2 |
RoundingMode::HALF_UP |
如果舍弃部分>=0.5则进位 | 1.5 到 2, 1.4 到 1, -1.5 到 -2, -1.4 到 -1 |
RoundingMode::HALF_DOWN |
如果舍弃部分>0.5则进位,否则向下舍入 | 1.6 到 2, 1.5 到 1, -1.6 到 -2, -1.5 到 -1 |
RoundingMode::HALF_EVEN |
更接近偶数的值被舍入到 | 1.5 到 2, 2.5 到 2, -1.5 到 -2, -2.5 到 -2 |
RoundingMode::HALF_FLOOR |
如果结果是正数,其行为与 HALF_DOWN 相同;如果结果是负数,其行为与 HALF_UP 相同 | 1.5 使用 RoundingMode::HALF_FLOOR 舍入会得到 1,-1.5 使用 RoundingMode::HALF_FLOOR 舍入会得到 -2 |
RoundingMode::UNNECESSARY |
断言请求的操作具有精确的结果,否则抛出异常 | 1.0 到 1, 1.5 则抛出异常 |
总结
建议在项目一开始,就规划好如何进行加减乘除,特别是涉及到金额的地方。
本作品采用《CC 协议》,转载必须注明作者和本文链接
讲的好,以前有个项目做预付费,第二个月结算,一分钱都不能有误,搞的头痛。
为什么官方不直接使用 bcmath 把运算符重载了?