讀源碼學MYSQL系列(一)decimal類型用法及存儲實現

問題來源

  最近在項目中用到了許多浮點數,精度要求較高,小數點後有4位甚至8位的,思考了一下,類似需求在工程計算、數值計算、股票金融、數字貨幣等場景都會出現。
  計算機提供了float/double兩種浮點類型的數據來進行科學計算,但計算機中的浮點數據表示是有誤差的,它們並不能準確的表示十進制的小數,在進行高精度計算時會產生誤差,再經過複雜的傳播,誤差就變得很不可控了。
  爲了保證結果的準確性,必須使用高精度計算。高精度計算的基本原理是模擬人工計算過程,保留計算過程中的所有數位,從而達到結果的精確性。各類語言及數據庫都提供了對基本浮點類型的支持,擴展庫都會提供相應的高精度數據的支持,在MYSQL中,decimal就是高精度浮點數據類型。後文主要介紹decimal的使用和實現原理。

MYSQL中浮點數據介紹

float/double

  MYSQL當中的float/double和我們常見的編程語言當中的float/double是一樣的,分別表示32位單精度和64位雙精度浮點數,在存儲上分別需要4字節和8字節。從浮點的特性考慮,float和double都只能近似表示,無法精確。如下圖所示,a列爲float(10, 4),b列爲double,參考第2行,同一個數131072.32保存在a和b的結果是不同的。在超出了浮點數的表示精度後,會有一定的截斷,從而引起計算結果的誤差。
在這裏插入圖片描述

numeric/decimal

基本用法

  decimal(M,D)表示高精度的小數,其中M表示整數加小數的數位,D表示小數部分位數,並且有如下約束:

字段 約束
M 總精度,整數加小數部分,1 <= M <= 65, 默認M = 10
D 小數部分精度,0 <= D <= 30且D <= M, 默認D = 0

  SQL標準中,numeric(M,D)表示準確爲M位的小數,而decimal(M,D)表示精度至少爲M,可以比M位多。但在MYSQL中,兩者是一樣的,都只能表示精度爲M位。

存儲實現

  MYSQL對decimal的存儲進行了優化。爲了節省空間,MYSQL採用4字節來存儲9位數位。我們知道,9位數字最大爲999999999,但4字節整數最大可以表示21億多,可以達到10位,所以4字節是充足的。整數部分和小數部分是分開存儲的,每9位存儲爲4字節,多餘部分採用額外的字節存儲。對應的額外字節如下:

數位 字節
0 0
1-2 1
3-4 2
5-6 3
7-9 4

  舉個例子,decimal(18,9)的整數部分和小數部分各有9位,所以兩邊各需要4字節來存儲。decimal(20,6)有14位整數,6位小數,整數部分先用4字節表示9位,餘下5位仍然需要3字節,所以整數部分共7個字節,小數部分則需要3字節。
  浮點位或者前綴0不會被保存。那麼MYSQL是怎麼保存負數的呢?負數的存儲是將正數的每個字節取反。參考下面的示例:
我們將1234567890.1234存儲到MYSQL中,設定M=14,D=4.
首先,將整數和小數進行分組:

1 234567890 1234

整數部分低9位可以存儲爲4個字節,即

...... 0D-FB-38-D2 ......

剩下的一位可以存儲成1個字節,

01 0D-FB-38-D2 ......

小數部分,可以用2字節存儲,得如下

01 0D-FB-38-D2 04-D2

對最高位求反,得到

81 0D-FB-38-D2 04-D2

於是,我們得到了這個14位精度數據在MYSQL中的二進制存儲

81 0D FB 38 D2 04 D2

對上述各個字節求反,可以得到-1234567890.1234的存儲表示

7E F2 04 C7 2D FB 2D

  由此可見,MYSQL中的decimal是可以實現對小數部分的高精度的,而且在性能上比起一般採用varchar存儲的做法要好,畢竟MYSQL內部採取的是整數分組計算的策略。這也啓發我們,如果要自己實現高精度計算,應該採取類似的思路。

  本文至此結束。本系列後續文章會結合源代碼分析MYSQL加減乘除的具體實現細節。

參考

DECIMAL Data Type Characteristics
DECIMAL數據類型特徵
github 源代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章