| 函數 | 功能 | 示例 |
| bcadd($left, $right, $scale?) | 加法 | bcadd('1.234', '5.678', 3) → '6.912' |
| bcsub($left, $right, $scale?) | 減法 | bcsub('5.000', '2.123', 3) → '2.877' |
| bcmul($left, $right, $scale?) | 乘法 | bcmul('1.5', '2.0', 2) → '3.00' |
| bcdiv($left, $right, $scale?) | 除法 | bcdiv('10', '3', 4) → '3.3333' |
| bcmod($left, $right) | 取模 | bcmod('10', '3') → '1' |
| bcpow($base, $exp, $scale?) | 冪 | bcpow('2', '10', 0) → '1024' |
| bcpowmod($base, $exp, $mod, $scale?) | 冪取模(數論/加密常用) | bcpowmod('2', '100', '1009') → '16' |
| bcsqrt($num, $scale?) | 平方根 | bcsqrt('2', 10) → '1.4142135623' |
| bccomp($left, $right, $scale?) | 比較:-1/0/1 | bccomp('1.000', '1.001', 3) → -1 |
| bcscale($scale) | 設置默認小數位 | bcscale(4); bcadd('1', '2') → '3.0000' |
實戰:解決浮點數精度缺失
<?php
// 原生浮點數:精度缺失
$a = 134.7 - 52.5 - 82.2;
var_dump($a); // float(-1.4210854715202E-14) 應為 0
// BCMath:精確計算
bcscale(2); // 設置默認 2 位小數
$aa = bcsub('134.7', '52.5', 1); // '82.2'
$bb = bcsub($aa, '82.2', 1); // '0.0'
echo $bb; // 0.0 精確!
// 封裝工具類
class Decimal {
private $scale = 10;
private $value = '0';
public function __construct($val, $scale = null) {
$this->value = (string)$val;
if ($scale !== null) $this->scale = $scale;
}
public function add($val) {
$this->value = bcadd($this->value, (string)$val, $this->scale);
return $this;
}
public function sub($val) {
$this->value = bcsub($this->value, (string)$val, $this->scale);
return $this;
}
public function mul($val) {
$this->value = bcmul($this->value, (string)$val, $this->scale);
return $this;
}
public function div($val) {
$this->value = bcdiv($this->value, (string)$val, $this->scale);
return $this;
}
public function cmp($val) {
return bccomp($this->value, (string)$val, $this->scale);
}
public function __toString() {
return rtrim(rtrim($this->value, '0'), '.');
}
}
// 鏈式調用
$price = (new Decimal('199.99'))
->mul('0.9') // 9 折
->sub('10') // 減 10 元優惠券
->add('0.01') // 手續費
->div('1.06'); // 含稅價反推
echo $price; // 精確結果
?>
金融場景最佳實踐
- 統一用「分」作為最小單位存儲:數據庫存
BIGINT(分),避免小數點漂移。展示時除以 100 格式化。
- 計算層強制 BCMath:任何涉及金額加減乘除、稅率、折扣、分攤,全部用 BCMath 或封裝類。
- scale 統一管理:項目級常量
DECIMAL_SCALE = 4(或按幣種區分),所有 BCMath 調用顯式傳遞或全局 bcscale()。
- 比較用 bccomp:
if (bccomp($a, $b, $scale) === 0) 替代 $a == $b。
- 四捨五入規則明確:BCMath 截斷不四捨五入,需自行實現:
bcadd($val, '0.005', 2) 實現保留 2 位四捨五入。
性能與替代方案
| 方案 | 精度 | 性能 | 適用場景 |
| BCMath | 任意精度 | 中等(C 實現) | 金融、電商、科學計算 |
| GMP | 大整數 | 高 | 大整數運算、加密 |
| 浮點數 + 四捨五入 | 近似 | 極高 | 非關鍵展示、圖表、近似統計 |
| brick/math (PHP 库) | 任意精度 | 中等 | 現代項目、面向對象、BigInteger/BigDecimal/Rational |
現代項目推薦 brick/math(Composer: composer require brick/math),提供 BigDecimal、BigInteger、Rational 等不可變值對象,API 更符合現代 PHP 風格,內部同樣基於 BCMath/GMP。
安裝與驗證
# Ubuntu/Debian
apt-get install php-bcmath
# CentOS/RHEL
yum install php-bcmath
# 驗證
php -m | grep bcmath
php -r "echo bcadd('0.1', '0.2', 1);" # 輸出 0.3
總結
**浮點數不等於數學實數**。任何涉及金額、精度敏感的計算,**零容忍使用原生浮點數**。BCMath 是 PHP 內建的任意精度利器,配合統一 scale 管理、封裝工具類、現代庫,構建可靠的數值計算基礎設施。記住:存「分」,算「BCMath」,比「bccomp」,展示「格式化」。
- 上一篇文章: PHP 魔術方法全景圖:從 __construct 到 __serialize 的完整指南
- 下一篇文章: PHP 獲取 QQ 在線狀態與昵稱:原理分析與多種實現方案
Copyright ©
程式碼之家 All Rights Reserved