读源码学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 源代码

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