Skip to content

BC Math 任意精度

PHP 的 BC Math 扩展提供了任意精度数学运算。标准的浮点数运算在处理大数或需要精确小数计算时会丢失精度,而 BC Math 可以精确控制小数位数,适合金融计算、科学计算等场景。

前置知识

阅读本节前,建议先了解:Math 数学函数PHP 类型系统

基础概念

为什么需要 BC Math

php
<?php
// 浮点数精度问题
echo 0.1 + 0.2;           // 0.30000000000000004
echo 0.1 + 0.2 === 0.3;  // false

// BC Math 精确计算
echo bcadd('0.1', '0.2', 1); // 0.3
echo bcmul('0.1', '0.2', 2); // 0.02

安装

bash
# 编译安装
./configure --enable-bcmath

# Ubuntu/Debian
sudo apt-get install php-bcmath

基本运算

四则运算

php
<?php
declare(strict_types=1);

// bcadd - 加法
echo bcadd('1.5', '2.3', 2) . PHP_EOL;    // 3.80
echo bcadd('1000000000000000', '1', 0) . PHP_EOL; // 1000000000000001

// bcsub - 减法
echo bcsub('5.5', '2.3', 2) . PHP_EOL;    // 3.20

// bcmul - 乘法
echo bcmul('1.5', '2.5', 2) . PHP_EOL;    // 3.75
echo bcmul('0.1', '0.2', 2) . PHP_EOL;    // 0.02

// bcdiv - 除法
echo bcdiv('10', '3', 4) . PHP_EOL;        // 3.3333
echo bcdiv('10', '0', 2) . PHP_EOL;        // Warning: Division by zero

// bcmod - 取余
echo bcmod('10', '3') . PHP_EOL;           // 1
echo bcmod('10.5', '3', 1) . PHP_EOL;      // 1.0 (PHP 7.2+)

// bcpow - 幂运算
echo bcpow('2', '10', 0) . PHP_EOL;        // 1024
echo bcpow('1.5', '3', 4) . PHP_EOL;       // 3.3750

// bcsqrt - 平方根
echo bcsqrt('2', 10) . PHP_EOL;            // 1.4142135623

// bcpowmod - 模幂运算
echo bcpowmod('2', '10', '1000', 0) . PHP_EOL; // 24 (2^10 mod 1000)

比较运算

php
<?php
declare(strict_types=1);

// bccomp - 比较两个数
// 返回: 0 (相等), 1 (第一个大于), -1 (第一个小于)
echo bccomp('1.5', '1.5', 2) . PHP_EOL;  // 0
echo bccomp('1.6', '1.5', 2) . PHP_EOL;  // 1
echo bccomp('1.4', '1.5', 2) . PHP_EOL;  // -1

// 比较带精度
echo bccomp('1.55', '1.556', 2) . PHP_EOL; // 0(只比较前2位小数)
echo bccomp('1.55', '1.556', 3) . PHP_EOL; // -1(比较前3位)

精度配置

bcscale 全局精度

php
<?php
declare(strict_types=1);

// 设置全局默认精度(小数位数)
bcscale(4);

// 设置后所有 bc 函数使用此精度
echo bcadd('1.1', '2.2') . PHP_EOL; // 3.3000
echo bcmul('1.1', '2.2') . PHP_EOL; // 2.4200

// 获取当前精度
$bcscale = bcscale();
echo "当前精度: {$bcscale}" . PHP_EOL;

// 重置
bcscale(0);

实战示例

金额计算

php
<?php
declare(strict_types=1);

class MoneyCalculator
{
    private int $scale;

    public function __construct(int $scale = 2)
    {
        $this->scale = $scale;
    }

    /**
     * 金额加法
     */
    public function add(string $a, string $b): string
    {
        return bcadd($a, $b, $this->scale);
    }

    /**
     * 金额减法
     */
    public function subtract(string $a, string $b): string
    {
        return bcsub($a, $b, $this->scale);
    }

    /**
     * 金额乘法(如计算价格 x 数量)
     */
    public function multiply(string $price, string $quantity): string
    {
        return bcmul($price, $quantity, $this->scale);
    }

    /**
     * 金额除法(如计算折扣价)
     */
    public function divide(string $amount, string $divisor): string
    {
        if ($divisor === '0' || $divisor === '0.0') {
            throw new RuntimeException("除数不能为零");
        }
        return bcdiv($amount, $divisor, $this->scale);
    }

    /**
     * 计算百分比
     */
    public function percentage(string $amount, string $percent): string
    {
        return bcdiv(bcmul($amount, $percent, $this->scale + 2), '100', $this->scale);
    }

    /**
     * 格式化金额显示
     */
    public function format(string $amount, string $currency = 'CNY'): string
    {
        $symbol = match ($currency) {
            'CNY' => '¥',
            'USD' => '$',
            'EUR' => '€',
            default => '',
        };
        return $symbol . number_format((float) $amount, $this->scale);
    }
}

// 使用示例
$calc = new MoneyCalculator(2);

$subtotal = $calc->multiply('99.90', '3');     // 299.70
$discount = $calc->percentage($subtotal, '15'); // 44.96
$total = $calc->subtract($subtotal, $discount); // 254.74

echo "小计: " . $calc->format($subtotal) . PHP_EOL;
echo "折扣: " . $calc->format($discount) . PHP_EOL;
echo "合计: " . $calc->format($total) . PHP_EOL;

注意事项

  • BC Math 所有参数都是字符串类型
  • $scale 参数控制结果的小数位数
  • bcdiv 除数为 0 时会报 Warning
  • BC Math 比 GMP 慢,但对于小数运算不可替代

下一节

继续学习:GMP 大数运算

参考链接