Watch at Ease看見世界,讀懂生活。

科技 · 實用指南

Decimal.quantize 出現 InvalidOperation?先檢查結果位數,再改精度

Decimal 精度限制整個係數,不是小數位數。透過隔離實驗分辨進位、補零與非有限輸入。

Decimal 結果容量原創示意圖
原創示意圖:捨入進位與必要補零都會占用係數位數。 Watch at Ease

quantize 的 InvalidOperation 可能來自所需結果的容量,而非輸入的小數位數。提高精度或關閉陷阱之前,先保留運算元與實際上下文。

1. 記錄運算,不只記錄例外名稱

保存完整追蹤訊息、兩個 Decimal 運算元及其來源。在失敗位置記錄 repr(value)、value.as_tuple()、repr(quantum)、getcontext().prec、rounding、Emin、Emax、traps 與既有 flags;檢查函式庫是否明確傳入另一個 context。在別處檢視的 DefaultContext 不一定是實際使用的上下文。

將建構與量化分開。由 float 建構的 Decimal 保留該浮點數的值,無法還原最初的十進位文字。輸入契約要求文字時,優先使用原始文字。格式錯誤、超出支援的數量級與後續 quantize 失敗是不同分支,不要用同一精度設定處理全部問題。

2. 計算進位與補零後的結果位數

精度限制係數總位數,不是小數點後的位數。CPython 3.12.14 的 ROUND_HALF_EVEN 實驗中,Decimal('9.995').quantize(Decimal('0.01')) 在精度 3 時失敗,精度 4 時回傳 10.00。結果需要四位係數;只預留一位整數與兩位小數,會漏算進位。

補零也占容量:Decimal('1.23').quantize(Decimal('0.0001')) 在精度 3 時失敗,精度 5 時回傳 1.2300。數值雖然沒變,必要尾零仍占係數位數。依支援的數量級、目標指數與可能進位設定工作精度,並核對指數上下限;沒有通用的萬用精度。

3. 保留無效值訊號,隔離變更

有限資料政策應先檢查 value.is_finite() 與 quantum.is_finite()。同組實驗中,靜默 NaN 直接傳播為 NaN,不拋出例外;sNaN 與 Infinity 對有限 quantum 量化則拋出 InvalidOperation。因此,只捕捉例外的流程仍可能接受非有限結果。

關閉 InvalidOperation 陷阱後,9.995 在精度 3 的失敗改為 NaN,並設定 InvalidOperation 旗標;這並沒有讓 10.00 裝得下。以 with localcontext() as ctx 限定運算範圍,只設定確認過的精度、捨入與陷阱政策,保留外圍上下文。若函式庫內部替換上下文,查明文件行為,別反覆改全域預設值。

4. 同時驗收數值、指數與訊號

在隔離上下文中,緊接受監測運算之前清除 flags,完成後讀取。有限輸出契約應同時要求 result.is_finite()、預期數值,以及 result.as_tuple().exponent 等於 quantum 的指數;明確決定是否允許 Inexact 與 Rounded。實驗中的 10.00 設定了兩種旗標,補零得到 1.2300 時兩者皆未設定;不拋例外不等於精確運算。

依應用程式真實規則重測一般輸入、跨整數邊界的進位、補零、負數、零、最大支援數量級與應拒絕的特殊值。我們的 8 組合成實驗涵蓋本文分支,並確認外圍精度還原;沒有驗證正式資料、財務規則或第三方函式庫。若隔離實驗仍失敗,保留運算元、上下文與旗標,診斷該分支,別吞掉例外。

Decimal 特殊值與陷阱實驗示意圖
原創實驗示意圖:沒有例外不代表結果有限或精確。 Watch at Ease