你不应该在项目中直接使用加减乘除

在我们的项目中,你不能直接使用加减乘除,而是应该使用 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 协议》,转载必须注明作者和本文链接
讨论数量: 4
空山

讲的好,以前有个项目做预付费,第二个月结算,一分钱都不能有误,搞的头痛。

1个月前 评论
苏近之 (楼主) 1个月前

为什么官方不直接使用 bcmath 把运算符重载了?

1个月前 评论
苏近之 (楼主) 1个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!