Yu Wu Hsien - Profile Picture
YU WU HSIEN

Just simple folk, with HTML, trying to make a living.

Laravel BCMath Cast:將精確計算整合進 Eloquent 模型

上一篇 文章中,展示了如何使用 PHP 8.4 的 BCMath Object API 解決浮點數精度問題,並實作了一個自定義 cast 來整合 BCMath\Number。

然而,每當建立新專案時,我發現自己總是在複製貼上相同的 cast 程式碼。這不僅重複勞動,也增加了維護成本 — 當需要修正錯誤或改進功能時,必須在多個專案中同步更新。 因此,我將這個 cast 封裝成一個小巧的 Laravel package:laravel-bcmath-cast

環境需求

  • PHP 8.4+(BCMath\Number 類別的必要條件)
  • Laravel 11+

安裝

shell
composer require yuwuhsien/laravel-bcmath-cast

使用

在 Eloquent 模型中加入 AsDecimal cast:

php
use YuWuHsien\Decimal\Casts\AsDecimal;

class Product extends Model
{
    protected function casts(): array
    {
        return [
            'price' => AsDecimal::class,
            'cost' => AsDecimal::class,
            'tax_rate' => AsDecimal::class,
        ];
    }
}

這些屬性會自動轉換為 BCMath\Number 物件,可以直接使用運算子進行精確計算:

php
$product = Product::find(1);

// 屬性已經是 BCMath\Number 實例
$product->price instanceof Number; // true

// 直接使用運算子進行精確計算
$margin = $product->price - $product->cost;
$marginPercent = ($margin / $product->cost) * new Number('100');

// 計算含稅總價
$total = $product->price * (new Number('1') + $product->tax_rate);

支援多種輸入型別

php
use BcMath\Number;

// BCMath\Number 物件
$product->price = new Number('29.99');

// 數值字串(推薦,保持精度)
$product->price = '29.99';

// 整數
$product->price = 30;

// 浮點數(可用但會觸發警告)
$product->price = 29.99;
// Warning: Float values may lose precision when converted to BCMath\Number.
// Use strings for exact decimal values (e.g., '19.99' instead of 19.99).

為什麼浮點數會觸發警告? 浮點數本身就存在精度問題,這正是使用 BCMath 的原因:

php
// 浮點數的精度問題
var_dump(0.1 + 0.2); // float(0.30000000000000004)

// 即使轉換成 BCMath\Number,也無法救回已經失去的精度
$num = new Number(0.1 + 0.2);
var_dump($num->value); // "0.30000000000000004"

因此,如果需要精確的計算,需要始終使用字串:

php
// 錯誤:使用浮點數
$product->price = 19.99;

// 正確:使用字串
$product->price = '19.99';

資料庫欄位類型

如果資料需要精確計算,資料庫欄位必須使用 DECIMAL 作為欄位類型,而非 FLOAT:

php
Schema::create('products', function (Blueprint $table) {
    $table->decimal('price', 10, 2);  // 正確:精確的小數儲存
    $table->decimal('cost', 10, 4);   // 正確:可指定不同精度
    $table->float('amount');          // 錯誤:會失去精度
});

更多相關的說明,可以參考 上一篇 文章。

最後

這個 package 源自我在 上一篇 文章中分享的實作經驗。將它獨立出來的目的很簡單:

讓需要精確計算的專案可以透過 Composer 安裝使用,而不需要每次都複製貼上程式碼。

如果你也在開發需要精確數值計算的應用(金融、會計、電商、庫存管理等),可以在你的專案中試試這個 package:

shell
composer require yuwuhsien/laravel-bcmath-cast
© 2025 Yu Wu Hsien.