「金額運算要用 bcmath,不要用浮點數」——這條規則寫在專案規範裡,隨便問哪個 AI coding agent,它都能立刻背出來:浮點數用二進位表示小數會有誤差,0.1 + 0.2 不等於 0.3,金額這種要求精確到分的場景不能冒這個險。
但「知道規則」跟「每一次都會主動套用規則」,是兩件不一樣的事。今天要講一個比前面幾篇更微妙的陷阱:當 AI 面對的不是一段全新程式碼,而是一段已經用浮點數運算、而且已經正式環境跑了很多年沒出過明顯問題的 legacy 邏輯時,它會不會主動指出這裡該改用 bcmath?
先講第一種常見狀況:重構一段涉及金額計算的 legacy 邏輯時,原本的寫法就是直接用 +、-、* 做浮點運算。AI 讀到這段程式碼,任務是「把這段邏輯搬到新的方法/新的架構裡,行為保持一致」,它很容易把注意力全部放在「行為要不要一致」這件事上,反而忽略了「這個行為本身在數值精度上其實是有瑕疵的,搬過去的時候該不該順手修正」。AI 傾向把「維持原邏輯不變」當成最安全的選項,卻沒意識到,繼續延用浮點運算,其實是把一個已知的風險原封不動地帶進新程式碼裡。
反過來也有第二種失敗:AI 讀過一次規則之後,變得「過度熱心」——只要看到任何帶小數點的數字運算,不管是不是金額,都套用 bcmath。日誌裡記錄一個百分比、UI 上顯示一個進度條的比例,這些場景本來就不要求精確到小數點後好幾位,硬套 bcmath 只是增加程式碼複雜度,甚至讓原本一行就能寫完的運算變成好幾行字串函式呼叫。
這兩種失敗表面上方向相反,根源卻是同一件事:AI 沒有真正判斷「這個數值在這個情境下要不要求精確」,而是用一個更省力的替代判斷——「原本就是浮點數就維持浮點數」或「看到小數點就套用 bcmath」——取代真正的判斷。
❌ 沿用原邏輯,沒有主動指出風險:
$total = $unitPrice * $quantity * (1 - $discountRate);
→ 這是金額運算,浮點誤差在多次運算疊加後可能造成分帳對不上,
AI 只是原樣搬過去,沒有標注這裡需要重新評估精度需求
❌ 過度套用,不需要精確度的地方也強制改寫:
$progressPercent = bcdiv(bcmul((string)$done, '100', 4), (string)$total, 2);
→ 進度條百分比不涉及金錢對帳,用浮點數完全足夠,
硬套 bcmath 只是增加不必要的字串轉換與可讀性成本
✅ 先判斷情境,再決定要不要套用:
// 金額運算:涉及對帳、需要精確到分 → 用 bcmath,並明確指定 scale
$total = bcmul(
bcmul($unitPrice, (string)$quantity, 4),
bcsub('1', $discountRate, 4),
2
);
// 非金額運算:只是顯示用途,浮點數精度足夠 → 維持浮點數,不用增加複雜度
$progressPercent = round(($done / $total) * 100, 2);
判斷標準不是「這是不是小數」,而是這個數值最終會不會被拿去對帳、加總、或者兩個系統之間互相核對——只要答案是會,就算現在看起來誤差很小,也要用 bcmath;只要答案是不會,浮點數的精度已經綽綽有餘,不需要為了規則而增加複雜度。
這系列前面提過,Repository 層宣告欄位型別時不能用命名前綴猜(nTotalAmt 這種命名看起來像整數,實際上資料庫欄位是 decimal),要求先查實際的資料表結構再宣告。bcmath 這條規則背後其實是同一個道理:不能用「這段程式碼看起來是什麼樣子」去判斷該怎麼處理,要回到「這個數值實際上代表什麼、會被怎麼使用」這個問題本身。
命名猜型別是靜態的線索誤導,浮點運算沿用舊邏輯則是「歷史慣性」的線索誤導——兩者都是 AI 傾向依賴表面訊號、跳過真正判斷的具體樣貌。這也再一次呼應 Day 01 的主題句:AI 給出的「已確認」結論,只在它實際查過的範圍內成立——如果 AI 只查了「這段程式碼原本長什麼樣子」,卻沒有查「這個數值在業務邏輯上要不要求精確」,那個「維持原樣應該沒問題」的結論,查證範圍本身就不夠。
金額運算要用高精度數值型別,這是語言無關的原則——Java 有 BigDecimal,Python 有 Decimal,JavaScript 生態常用 decimal.js 這類函式庫,講的都是同一件事:不要讓涉及金錢對帳的數值,暴露在二進位浮點數的表示誤差之下。bcmath 只是 PHP 標準函式庫提供的具體工具,換一個語言環境,工具會不一樣,但「這個數值需不需要精確」這個判斷本身,是完全可以遷移的。
到這裡,第二部(Day 6-15)算是告一段落。這十天講的東西,表面上很分散——乾淨基準比對、獨立 subagent、Repository 分層、外部 API 介面化、bcmath——但背後其實是同一條主線:把 AI 需要憑感覺判斷的空間,一點一點收斂成可以被驗證、可以被檢查的具體邊界。 覆蓋率報告告訴 AI 這一行能不能動,Repository 介面讓 AI 不用自己組危險的 SQL 字串,PSR-17/18 讓外部依賴變得可以被 mock 掉,bcmath 讓「這個數值要不要求精確」變成一個有明確答案的問題。
這些機制單獨看都是技術細節,合起來看,才是這系列真正想講的東西:流程設計的目標,從來不是讓 AI 變得更聰明,而是讓它需要自己判斷的範圍越來越小。
回想你手上的專案裡,有沒有哪段數值運算是「因為原本就這樣寫,所以沒特別想過要不要改」?那段運算最終的結果,會不會被拿去跟另一個系統對帳?
第二部到這裡結束,明天進入第三部:AI Agent 的長期記憶與團隊協作。第一篇要講的是 CLAUDE.md 跟 skill 之間怎麼分工——為什麼有些規則放在「每次都要讀」的地方,有些規則卻要設計成「只有碰到特定情境才載入」。