天天看點

讀源碼學MYSQL系列(一)decimal類型用法及存儲實作問題來源MYSQL中浮點資料介紹參考

問題來源

  最近在項目中用到了許多浮點數,精度要求較高,小數點後有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的結果是不同的。在超出了浮點數的表示精度後,會有一定的截斷,進而引起計算結果的誤差。

讀源碼學MYSQL系列(一)decimal類型用法及存儲實作問題來源MYSQL中浮點資料介紹參考

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位元組,多餘部分采用額外的位元組存儲。對應的額外位元組如下:

數位 位元組
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 源代碼