小巧、快速、可靠。
選擇其中三項。
浮點數

1. SQLite 如何儲存數字

SQLite 以 64 位元二補數格式¹ 儲存整數值。這提供了 -9223372036854775808 到 +9223372036854775807 的儲存範圍(含)。此範圍內的整數是精確的。

所謂的「REAL」或浮點值以IEEE 754 雙精度二進位格式¹ 儲存。這提供了大約 1.7976931348623157e+308 到 4.9406564584124654e-324 的正值範圍,以及等效的負值範圍。雙精度浮點數也可以是 0.0(和 -0.0)、正負無限大以及「NaN」或「非數值」。浮點值是近似值。

請密切注意上一段的最後一句

浮點值是近似值。

如果您需要精確的答案,則不應使用雙精度浮點值,無論是在 SQLite 中還是在任何其他產品中。這不是 SQLite 的限制。這是浮點數設計中固有的數學限制。


¹ 例外:R-Tree 擴充功能將資訊儲存為 32 位元浮點數或整數值。

1.1. 浮點數精度

SQLite 保證保留浮點值最顯著的 15 位數。然而,它不保證浮點數計算的準確性,因為不可能做出這樣的保證。對浮點值執行數學運算會引入誤差。例如,考慮如果您嘗試減去兩個大小相似的浮點數會發生什麼情況

1152693165.1106291898
-1152693165.1106280772

0.0000011126

上面顯示的結果 (0.0000011126) 是正確答案。但是,如果您使用雙精度浮點數進行此計算,您得到的答案是 0.00000095367431640625 - 誤差約為 14%。如果您的程式中執行了許多類似的計算,這些誤差會累積,因此您的最終結果可能完全沒有意義。

之所以會出現誤差,是因為每個數字中只有大約前 15 位有效數字被準確儲存,而被減去的兩個數字之間的第一個差異出現在第 16 位。

1.2. 浮點數

雙精度浮點數格式每個數字使用 64 位元。因此,有 1.845e+19 個不同的可能浮點值。另一方面,在 1.7977e+308 和 4.9407e-324 的範圍內有無數個實數。因此,雙精度浮點數不可能表示該範圍內所有可能的實數。需要近似值。

IEEE 754 浮點值是一個整數乘以 2 的冪

M × 2E

M 值是「尾數」,E 是「指數」。M 和 E 都是整數。

對於雙精度浮點數,M 是一個 53 位元的整數,E 是一個 11 位元的整數,其偏移量使其表示 -1074 到 +972(含)之間的值範圍。

(備註:IEEE 754 的一般描述更為複雜,如果您真的想了解 IEEE 754 的細節、優點和限制,理解這些額外的複雜性非常重要。然而,這裡顯示的整數描述雖然不完全正確,但更容易理解,並且足以滿足本文的目的。)

1.2.1. 無法精確表示的數字

並非所有有效位數少於 16 位的十進位數字都能以 binary64 格式精確表示。事實上,大多數帶有小數點後位數的十進位數字都缺乏精確的 binary64 等效值。例如,如果您有一個資料庫欄位用於儲存以美元和美分表示的商品價格,則唯一可以精確表示的美分值是 0.00、0.25、0.50 和 0.75。小數點右側的任何其他數字都會導致近似值。如果您提供「價格」值 47.49,則該數字將以 binary64 表示為

6683623321994527 × 2-47

計算結果為

47.49000000000000198951966012828052043914794921875

該數字非常接近 47.49,但不完全相同。它稍微大了一點。如果我們將 M 減 1,變為 6683623321994526,以便得到下一個較小的 binary64 值,我們將得到

47.4899999999999948840923025272786617279052734375

第二個數字太小了。第一個數字更接近所需值 47.49,因此會使用第一個數字。但它並不精確。大多數十進位值在 IEEE 754 中都是這樣運作的。請記住我們上面提到的重點

浮點值是近似值。

如果您對浮點值只記得一件事,請務必記住這個關鍵概念。

1.2.2. 它夠接近嗎?

IEEE 754 Binary64 提供的精度足以應付大多數計算。例如,如果「47.49」代表價格,且通貨膨脹率為每年 2%,則價格每秒上漲約 0.0000000301 美元。記錄值 47.49 中的誤差大約相當於 66 奈秒的通貨膨脹。因此,如果您輸入的 47.49 價格是精確的,那麼通貨膨脹的影響將導致實際值與實際儲存的值 (47.4900000000000019895196601282805204391479492187) 在不到千萬分之一秒的時間內完全相等。對於大多數用途而言,這樣的精度肯定足夠了吧?

2. 處理浮點數的擴充功能

2.1. ieee754.c 擴充功能

ieee754 擴充功能可在浮點數的 binary64 表示形式與 M×2E 格式之間進行轉換。換句話說,在表達式

F = M × 2E

中,ieee754 擴充功能可在 F 與 (M,E) 之間進行相互轉換。

ieee754 擴充功能不屬於合併檔案的一部分,但預設包含在命令列介面中。如果您想在應用程式中包含 ieee754 擴充功能,則需要單獨編譯和載入它。

2.1.1. ieee754() 函式

ieee754(F) SQL 函式接受單個浮點數參數作為輸入,並返回如下所示的字串

'ieee754(M,E)'

其中 M 和 E 分別由浮點數的尾數和指數取代。例如

sqlite> .mode box
sqlite> SELECT ieee754(47.49) AS x;
┌───────────────────────────────┐
│               x               │
├───────────────────────────────┤
│ ieee754(6683623321994527,-47) │
└───────────────────────────────┘

反過來,ieee754() 的雙參數版本接受 M 和 E 值,並將它們轉換為對應的 F 值

sqlite> select ieee754(6683623321994527,-47) as x;
┌───────┐
│   x   │
├───────┤
│ 47.49 │
└───────┘

2.1.2. ieee754_mantissa() 和 ieee754_exponent() 函式

單參數形式的 ieee754() 的文字輸出非常易於閱讀,但在較大表達式中使用卻很麻煩。因此,新增了 ieee754_mantissa() 和 ieee754_exponent() 常式,以返回對應於其單個參數 F 值的 M 和 E 值。例如

sqlite> .mode box
sqlite> SELECT ieee754_mantissa(47.49) AS M, ieee754_exponent(47.49) AS E;
┌──────────────────┬─────┐
│        M         │  E  │
├──────────────────┼─────┤
│ 6683623321994527 │ -47 │
└──────────────────┴─────┘

2.1.3. ieee754_from_blob() 和 ieee754_to_blob() 函式

ieee754_to_blob(F) SQL 函式將浮點數 F 轉換為 8 位元組的 BLOB,該 BLOB 是該數字的大端序 binary64 編碼。ieee754_from_blob(B) 函式則相反,將 8 位元組的 BLOB 轉換為 binary64 編碼所表示的浮點值。

例如,如果您在維基百科上讀到最小正 binary64 值的編碼為 0x0000000000000001,則您可以像這樣找到對應的浮點值

sqlite> .mode box
sqlite> SELECT ieee754_from_blob(x'0000000000000001') AS F;
┌───────────────────────┐
│           F           │
├───────────────────────┤
│ 4.94065645841247e-324 │
└───────────────────────┘

或者反過來

sqlite> .mode box
sqlite> SELECT quote(ieee754_to_blob(4.94065645841247e-324)) AS binary64;
┌─────────────────────┐
│      binary64       │
├─────────────────────┤
│ X'0000000000000001' │
└─────────────────────┘

2.2. decimal.c 擴充

decimal 擴充功能提供對以文字字串儲存的數字進行任意精度的十進位算術運算。由於數字是以任意精度和文字形式儲存的,因此不需要近似值。計算可以精確完成。

decimal 擴充功能(目前)不是 SQLite 合併檔案 的一部分。但是,它包含在 命令列介面 中。

有三個數學函式可用

前三個函式分別加、減和乘以它們的參數,並返回一個新的文字字串,該字串是結果的十進位表示形式。參數被解釋為文字。沒有除法運算子,因為十進位除法通常不會產生有限的十進位結果。

decimal_pow2(N) 函式返回 2.0 的 N 次方,其中 N 是介於 -20000 和 +20000 之間的整數。

使用 decimal_cmp(A,B) 來比較兩個十進位值。如果 A 小於、等於或大於 B,則結果將分別為負數、零或正數。

decimal_sum(X) 函式是一個聚合函式,類似於內建的 sum() 聚合函式,不同之處在於 decimal_sum() 以任意精度計算其結果,因此是精確的。

decimal 擴充功能提供「decimal」排序序列,以數值順序比較十進位文字字串。

decimal(X) 和 decimal_exp(X) 產生輸入 X 的十進位表示形式。decimal_exp(X) 函式以指數表示法(末尾帶有「e+NN」)返回結果,而 decimal(X) 返回純十進位數(不帶「e+NN」)。如果輸入 X 是浮點值,則會將其展開為其精確的十進位等值。例如

sqlite> .mode qbox
sqlite> select decimal(47.49);
┌──────────────────────────────────────────────────────┐
│                    decimal(47.49)                    │
├──────────────────────────────────────────────────────┤
│ '47.49000000000000198951966012828052043914794921875' │
└──────────────────────────────────────────────────────┘

本頁面最後修改時間:世界協調時間 2024-07-25 10:34:20