萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> MySQL 教學

MySQL FLOAT 單精度浮點數誤差深度解析:IEEE 754 原理、儲存陷阱、金額欄位選型與跨語言對照

MySQL FLOAT (單精度) 與 DOUBLE (雙精度) 遵循 IEEE 754 標準,以二進位近似表示十進位實數,**天然存在精度誤差**。本文從二進位表示原理、MySQL/Oracle 類型對照、實測誤差案例、程式語言同源問題、金額欄位選型鐵律五個維度深度剖析,建立正確數值類型選型觀念。

IEEE 754 浮點數表示與誤差來源

單精度 (FLOAT, 4 Bytes = 32 bits)

  • 1 bit 符號位
  • 8 bits 指數 (Exponent, 偏移 127)
  • 23 bits 尾數 (Mantissa/Significand) + 1 隱含首位 1 → **實際 24 bits 精度**

雙精度 (DOUBLE, 8 Bytes = 64 bits)

  • 1 bit 符號位
  • 11 bits 指數 (偏移 1023)
  • 52 bits 尾數 + 1 隱含首位 → **實際 53 bits 精度**

十進位有效數字對應

  • FLOAT:~7 位有效數字 (2^24 ≈ 1.67e7)
  • DOUBLE:~15-16 位有效數字 (2^53 ≈ 9.0e15)

兩大誤差來源

  1. 位數截斷誤差: 有效數字超過精度上限 (如 24 位整數 1310720000000000000000.66) 導致低位丟失。
  2. 進制轉換誤差: 十進位小數無法用有限二進位精確表示 (如 7.22、0.1、131072.32),捨入產生誤差。

MySQL 實測誤差案例

CREATE TABLE tt (num FLOAT(9,3));
INSERT INTO tt VALUES (123456.8);   -- Out of range (超過 9 位總長度)
INSERT INTO tt VALUES (123456.8);   -- 成功
SELECT * FROM tt;                   -- 123456.797  (近似值!)
INSERT INTO tt VALUES (123456.867); -- 123456.867 (精確,因在精度內)
INSERT INTO tt VALUES (2.8);        -- 2.800
INSERT INTO tt VALUES (2.888888);   -- 2.889 (四捨五入)

關鍵發現:

  • 超出定義精度 (M,D) 會報錯或截斷。
  • 未超出精度但二進位無法精確表示時,產生**近似值** (如 123456.797 vs 123456.8)。
  • WHERE num = 2.8 可匹配,但 WHERE num = 123456.8 **無法匹配** 存儲的 123456.797。

MySQL 與 Oracle 數值類型對照

分類MySQLOracle
IEEE 754 單精度FLOATBINARY_FLOAT
IEEE 754 雙精度DOUBLE / REALBINARY_DOUBLE
ANSI FLOAT(p)FLOAT(p) (p≤24 單精度, p>24 雙精度)FLOAT / FLOAT(n)
定點數 (精確)DECIMAL(p,s) / NUMERIC(p,s)NUMBER(p,s)
無精度指定浮點-NUMBER (預設 38 位精度)

**結論:** 只要遵循 IEEE 754,所有資料庫/語言的浮點數**都有相同精度問題**,非 MySQL 獨有。

程式語言同源問題 (Java/C#/PHP/Python/Go...)

// Java
System.out.println(7.22f - 7.0f);  // 0.21999979 ≠ 0.22

// PHP
echo 7.22 - 7.0;   // 0.22 (PHP 預設雙精度顯示截斷)
var_dump(7.22 - 7.0); // float(0.21999999999999997)

// Python
print(7.22 - 7.0)  // 0.21999999999999997

// Go
fmt.Println(7.22 - 7.0) // 0.21999999999999997

鐵律: **絕不直接比較浮點數相等** (if (a == b)),改用誤差範圍 abs(a-b) < epsilon;**貨幣/精確計算嚴禁浮點數**。

金額欄位選型鐵律

場景推薦類型理由
貨幣金額 (元/分/厘)DECIMAL(10,2) / BIGINT (存分)精確定點運算,無捨入誤差
科學測量、坐標、物理量DOUBLE15 位有效數字滿足多數科學計算
一般業務數值、比率、百分比DECIMAL(10,4) 或 DOUBLE視精度要求權衡
極大整數 (雪花ID、遞增ID)BIGINT UNSIGNED64 位整數,無精度損失

MySQL 5.0+ 針對 FLOAT 查詢失效的修正

-- 問題:WHERE float_col = 2.2 無法匹配存儲的 2.2000000476837158
-- 方案 1:改用 DOUBLE (較大精度,但仍有誤差)
-- 方案 2:指定格式化比較 (僅限小數位 ≤ 6)
SELECT * FROM t WHERE FORMAT(float_col, 3) = FORMAT(2.2, 3);
-- 方案 3:範圍查詢 (推薦)
SELECT * FROM t WHERE float_col BETWEEN 2.1999 AND 2.2001;
-- 方案 4:遷移至 DECIMAL (徹底解決)
ALTER TABLE t MODIFY float_col DECIMAL(10,3);

總結

  1. 浮點數誤差源於 **IEEE 754 二進位表示本質**,跨資料庫、跨語言通用。
  2. FLOAT 7 位有效數字,**超過 ±131072 (2^17) 精度急劇下降**;DOUBLE 15 位較安全但仍不精確。
  3. 貨幣、財務、精確計算 → 唯一正解:DECIMAL 或整數(分/厘)。
  4. 程式中**避免浮點數 == 比較**,使用 epsilon 範圍或定點庫。
  5. 資料庫設計階段即確定數值類型,**事後修改成本極高**;寧可多佔幾位元組用 DECIMAL,不可留隱患。
copyright © 萬盛學電腦網 all rights reserved