Navicat 部落格

MySQL 中的浮點數捨入錯誤 2021 年 9 月 3 日,由 Robert Gravelle 撰寫

儘管 MySQL DECIMAL 和 NUMERIC 資料類型都是定點值,但它們仍然容易出現捨入錯誤。原因是,無論一個類型可以容納多少位數(DECIMAL 的最大位數是 65!),這個數字仍然是固定的。此外,DECIMAL 欄可以被指定精確度或小數位數,這可能會對允許的位數產生截斷的潜在影響。

在 Navicat 資料表設計器中的 DECIMAL 欄
decimal_column_in_navicat (43K)

當一名讀者問我為什麼兩個類似的査詢在計算中傳回的 DECIMAL 數值略有不同時,我意識到 MySQL 中可能存在捨入錯誤。這促使我踏上了探索之旅。在今天的文章中,我想分享一些我在 MySQL 中浮點數捨入的了解。

兩個查詢的故事

以下是我用來表示差異的査詢:

使用子査詢計算每小時付款
subquery (37K)
使用 GROUP BY WITH ROLLUP 計算每小時付款
group_by (57K)

注意:從 GROUP BY WITH ROLLUP 查詢中刪除了一些列,以降低圖像的高度。

讀者正在計算員工薪水,但我沒有相同的資料表可査詢,因此我使用了我能找到的最相似的資料表,即 classicmodels範例資料庫的 payments 資料表:

payments_table (114K)

在這種情況下,計算每小時付款可能沒有多大意義,但査詢確實突顯出兩個 SELECT 陳述式之間的捨入差異(4256.653475 與 4256.653476)。

那麼,為什麼使用子査詢和 GROUP BY WITH ROLLUP 進行 SUM 時會產生不同的結果呢?

浮點近似數值與定點精確數值比較

浮點(近似值)類型是 FLOAT、REAL 和 DOUBLE,而定點(精確值)類型是 INTEGER、SMALLINT、DECIMAL 和 NUMERIC。浮點數意味著小數點可以放在相對於數字的有效位數的任何位置,實際位置單獨表示。同時,定點值是按特定因數縮放的整數。

在版本 5.5 中,MySQL 新增了對精確數學的支援,其中包括一個定點數算術函式庫,它取代了基礎層的 C 函式庫,並允許在不同的平台上以相同的方式處理作業。自此更新以來,如果計算中未使用近似值或字串,則使用精確度為 65 位數的 DECIMAL 精確值算術計算運算式。對於 GROUP BY 函式,STDDEV() 和 VARIANCE() 傳回 DOUBLE,這是一種近似浮點類型,而 SUM() 和 AVG() 為精確值參數傳回 DECIMAL,為近似值傳回 DOUBLE。

用於定點數運算的新 MySQL 函式庫的另一個分支是現在使用浮點值處理類型轉換。因此,類型轉換的結果可能會有所不同,並且會受到電腦體系結構、編譯器版本甚至最佳化層級等因素的影響。避免這些問題的一種方法是使用明確 CAST() 而不是隱含轉換。

哪個結果是正確的?

回到最初的査詢,哪一個值更準確,哪一個査詢是獲得 SUM 的正確方法?事實上,兩者都不精確,但是,透過使用一些代數,可以簡化査詢以產生準確的結果:

simplified_query (30K)

令四捨五入精確的關鍵是盡可能在初始步驟中使用整數。

Navicat 文章
頻道記錄
分享
部落格封存檔