# DECIMAL 数据处理原理浅析

The declaration syntax for a DECIMAL column is DECIMAL(M,D). The ranges of values for the arguments are as follows:
M is the maximum number of digits (the precision). It has a range of 1 to 65.

D is the number of digits to the right of the decimal point (the scale). It has a range of 0 to 30 and must be no larger than M.

If D is omitted, the default is 0. If M is omitted, the default is 10.

The maximum value of 65 for M means that calculations on DECIMAL values are accurate up to 65 digits. This limit of 65 digits of precision also applies to exact-value numeric literals, so the maximum range of such literals differs from before. (There is also a limit on how long the text of DECIMAL literals can be; see Section 12.25.3, “Expression Handling”.)

[En]

The maximum precision and decimal places mentioned in the above materials are the focus of this paper.

1. 最大精度是 65
2. 小数位最多 30

1. MySQL 中当使用 SELECT 查询常数时，例如： SELECT 123456789.123; 是如何处理的？
2. MySQL 中查询一下两条语句分别返回结果是多少？为什么？
SELECT 111111111111111111111111111111111111111111111111111111111111111111111111111111111;
SELECT 1111111111111111111111111111111111111111111111111111111111111111111111111111111111;


## MySQL 如何解析常数

static inline uint int_token(const char *str, uint length) {
...

if (neg) {
cmp = signed_long_str + 1;
smaller = NUM;      // If = signed_long_str
} else if (length < signed_longlong_len)
return LONG_NUM;
else if (length > signed_longlong_len)
return DECIMAL_NUM;
else {
cmp = signed_longlong_str + 1;
smaller = LONG_NUM;  // If  longlong_len) {
if (length > unsigned_longlong_len) return DECIMAL_NUM;
cmp = unsigned_longlong_str;
smaller = ULONGLONG_NUM;
bigger = DECIMAL_NUM;
} else {
cmp = longlong_str;
smaller = LONG_NUM;
bigger = ULONGLONG_NUM;
}
}
while (*cmp && *cmp++ == *str++)
;
return ((uchar)str[-1]


neg表示是否是负数，直接看正数的处理分支，负数同理：

• 当输入的数值串长度等于 10 时 MySQL 可能使用 LONG_NUMLONG_NUM 表示
• 当输入的数值串长度小于 19 时 MySQL 使用 LONG_NUM 表示
• 当输入的数值串长度等于 20 时 MySQL 可能使用 LONG_NUMDECIMAL_NUM 表示
• 当输入的数值串长度大于 20 时 MySQL 使用 DECIMAL_NUM 表示
• 其他长度时，MySQL 可能使用 LONG_NUMULONGLONG_NUM 表示

static const char *long_str = "2147483647";
static const uint long_len = 10;
static const char *signed_long_str = "-2147483648";
static const char *longlong_str = "9223372036854775807";
static const uint longlong_len = 19;
static const char *signed_longlong_str = "-9223372036854775808";
static const uint signed_longlong_len = 19;
static const char *unsigned_longlong_str = "18446744073709551615";
static const uint unsigned_longlong_len = 20;


3. 上面分析提到的 DECIMAL 是否与官方文档中提到的 DECIMAL 类型或者换一种方式说：是否与建表语句 CREATE TABLE t(d DECIMAL(65, 30)); 中字段 dDECIMAL(65, 30)类型(可以不考虑精度和小数位)相同？

## MySQL 解析 DECIMAL 常数时怎么处理溢出

root@mysqldb 14:09:  [(none)]> SELECT 111111111111111111111111111111111111111111111111111111111111111111111111111111111;
+-----------------------------------------------------------------------------------+
| 111111111111111111111111111111111111111111111111111111111111111111111111111111111 |
+-----------------------------------------------------------------------------------+
| 111111111111111111111111111111111111111111111111111111111111111111111111111111111 |
+-----------------------------------------------------------------------------------+
1 row in set (2.28 sec)

root@mysqldb 14:09:  [(none)]> SELECT 1111111111111111111111111111111111111111111111111111111111111111111111111111111111;
+------------------------------------------------------------------------------------+
| 1111111111111111111111111111111111111111111111111111111111111111111111111111111111 |
+------------------------------------------------------------------------------------+
|                  99999999999999999999999999999999999999999999999999999999999999999 |
+------------------------------------------------------------------------------------+
1 row in set, 1 warning (2.01 sec)


[En]

Then the above train of thought goes down to the syntax parsing of the constant:

NUM_literal:
int64_literal
| DECIMAL_NUM
{
$$= NEW_PTN Item_decimal(@, 1.str, 1.length, YYCSCL); } | FLOAT_NUM {$$= NEW_PTN Item_float(@$,$1.str, \$1.length);
}
;


[En]

Before analyzing the code, let’s take a look at a few constant definitions:

/** maximum length of buffer in our big digits (uint32). */
static constexpr int DECIMAL_BUFF_LENGTH{9};

/** the number of digits that my_decimal can possibly contain */
static constexpr int DECIMAL_MAX_POSSIBLE_PRECISION{DECIMAL_BUFF_LENGTH * 9};

/**
maximum guaranteed precision of number in decimal digits (number of our
digits * number of decimal digits in one our big digit - number of decimal
digits in one our big digit decreased by 1 (because we always put decimal
point on the border of our big digits))
*/
static constexpr int DECIMAL_MAX_PRECISION{DECIMAL_MAX_POSSIBLE_PRECISION -
8 * 2};

static constexpr int DECIMAL_MAX_SCALE{30};

• DECIMAL_BUFF_LENGTH：表示整个 DECIMAL 类型数据的缓冲区大小
• DECIMAL_MAX_POSSIBLE_PRECISION：每个缓冲区单元可以存储 9 位数字，所以最大可以处理的精度这里为 81
• DECIMAL_MAX_PRECISION：用来限制官方文档介绍中 decimal(M,D) 中的 M 的最大值，亦或是当超大常数溢出后返回的整数部分最大长度
• DECIMAL_MAX_SCALE：用来限制官方文档介绍中 decimal(M,D) 中的 D 的最大值
Item_decimal::Item_decimal(const POS &pos, const char *str_arg, uint length,
const CHARSET_INFO *charset)
: super(pos) {
str2my_decimal(E_DEC_FATAL_ERROR, str_arg, length, charset, &decimal_value);
item_name.set(str_arg);
set_data_type(MYSQL_TYPE_NEWDECIMAL);
decimals = (uint8)decimal_value.frac;
fixed = true;
max_length = my_decimal_precision_to_length_no_truncation(
decimal_value.intg + decimals, decimals, unsigned_flag);
}


Item_decimal构造函数中调用 str2my_decimal函数对输入数值进行处理，将其转换为 my_decimal类型的数据。

int str2my_decimal(uint mask, const char *from, size_t length,
const CHARSET_INFO *charset, my_decimal *decimal_value) {
const char *end, *from_end;
int err;
char buff[STRING_BUFFER_USUAL_SIZE];
String tmp(buff, sizeof(buff), &my_charset_bin);
if (charset->mbminlen > 1) {
uint dummy_errors;
tmp.copy(from, length, charset, &my_charset_latin1, &dummy_errors);
from = tmp.ptr();
length = tmp.length();
charset = &my_charset_bin;
}
from_end = end = from + length;
err = string2decimal(from, (decimal_t *)decimal_value, &end);
if (end != from_end && !err) {
/* Give warning if there is something other than end space */
for (; end < from_end; end++) {
if (!my_isspace(&my_charset_latin1, *end)) {
err = E_DEC_TRUNCATED;
break;
}
}
return err;
}


str2my_decimal 函数先将数值字符串转为合适的字符集后，调用 string2decimal 函数将数值字符串转为 decimal_t 类型的数据。 my_decimal 类型和 decimal_t 类型的关系如下：

@startuml

class decimal_t
{
+ int intg, frac, len;
+ bool sign;
+ decimal_digit_t *buf;
}

class my_decimal
{
- decimal_digit_t buffer[DECIMAL_BUFF_LENGTH];
}

decimal_t

• decimal_digit_tint32_t 的别名
• intg 表示整数部分的字符个数
• frac 表示小数部分的字符个数
• sign 表示是否负数
• buf 指向 buffer
• buffer 是数据存放数组，数组长度为 9，也就意味着一个 decimal 最多可以存放 9int32_t 大小的数据，但由于设计限制每个数组元素限制存储 9 个字符，因此 buffer 最多可以存储 81个字符

string2decimal 的代码实现：

int string2decimal(const char *from, decimal_t *to, const char **end) {
const char *s = from, *s1, *endp, *end_of_string = *end;
int i, intg, frac, error, intg1, frac1;
dec1 x, *buf;
sanity(to);

error = E_DEC_BAD_NUM; /* In case of bad number */
while (s < end_of_string && my_isspace(&my_charset_latin1, *s)) s++;
if (s == end_of_string) goto fatal_error;

if ((to->sign = (*s == '-')))
s++;
else if (*s == '+')
s++;

s1 = s;
while (s < end_of_string && my_isdigit(&my_charset_latin1, *s)) s++;
intg = (int)(s - s1);
if (s < end_of_string && *s == '.') {
endp = s + 1;
while (endp < end_of_string && my_isdigit(&my_charset_latin1, *endp))
endp++;
frac = (int)(endp - s - 1);
} else {
frac = 0;
endp = s;
}

*end = endp;

if (frac + intg == 0) goto fatal_error;

error = 0;

intg1 = ROUND_UP(intg);
frac1 = ROUND_UP(frac);
FIX_INTG_FRAC_ERROR(to->len, intg1, frac1, error);
if (unlikely(error)) {
frac = frac1 * DIG_PER_DEC1;
if (error == E_DEC_OVERFLOW) intg = intg1 * DIG_PER_DEC1;
}

/* Error is guranteed to be set here */
to->intg = intg;
to->frac = frac;

buf = to->buf + intg1;
s1 = s;

for (x = 0, i = 0; intg; intg--) {
x += (*--s - '0') * powers10[i];

if (unlikely(++i == DIG_PER_DEC1)) {
*--buf = x;
x = 0;
i = 0;
}
}
if (i) *--buf = x;

buf = to->buf + intg1;
for (x = 0, i = 0; frac; frac--) {
x = (*++s1 - '0') + x * 10;

if (unlikely(++i == DIG_PER_DEC1)) {
*buf++ = x;
x = 0;
i = 0;
}
}
if (i) *buf = x * powers10[DIG_PER_DEC1 - i];

/* Handle exponent */
if (endp + 1 < end_of_string && (*endp == 'e' || *endp == 'E')) {
int str_error;
longlong exponent = my_strtoll10(endp + 1, &end_of_string, &str_error);

if (end_of_string != endp + 1) /* If at least one digit */
{
*end = end_of_string;
if (str_error > 0) {
goto fatal_error;
}
if (exponent > INT_MAX / 2 || (str_error == 0 && exponent < 0)) {
error = E_DEC_OVERFLOW;
goto fatal_error;
}
if (exponent < INT_MIN / 2 && error != E_DEC_OVERFLOW) {
error = E_DEC_TRUNCATED;
goto fatal_error;
}
if (error != E_DEC_OVERFLOW) error = decimal_shift(to, (int)exponent);
}
}
/* Avoid returning negative zero, cfr. decimal_cmp() */
if (to->sign && decimal_is_zero(to)) to->sign = false;
return error;

fatal_error:
decimal_make_zero(to);
return error;
}


1. 分别计算整数部分和小数部分各有多少个字符
2. 分别计算整数部分和小数部分各需要多少个 buffer 元素来存储
3. 如果整数部分需要的 buffer 元素个数超过 9，则表示溢出
4. 如果整数部分和小数部分需要的 buffer 元素个数超过 9，则表示需要将小数部分进行截断
由于先解析整数部分，再解析小数部分，因此，如果整数部分如果完全占用所有 buffer 元素，此时，小数部分会被截断。
5. 将整数部分和小数部分按每 9 个字符转为一个整数记录到 buffer 的元素中（ buffer中的模型示例如下）
&#x4F8B;&#x5982;&#x5E38;&#x6570;&#xFF1A;111111111222222222333333333.444444444

intg = 27, frac = 9, len = 9, sign = false
byte         0            1           2           3          4         5         6         6         7         8
buffer: | 111111111 | 222222222 | 333333333 | 444444444 | UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN |


check_result_and_overflow 代码实现:

void max_decimal(int precision, int frac, decimal_t *to) {
int intpart;
dec1 *buf = to->buf;
assert(precision && precision >= frac);

to->sign = false;
// 发生溢出时将 buffer 中的数据更新为 9 99 999 ...

if ((intpart = to->intg = (precision - frac))) {
int firstdigits = intpart % DIG_PER_DEC1;
if (firstdigits) *buf++ = powers10[firstdigits] - 1; /* get 9 99 999 ... */
for (intpart /= DIG_PER_DEC1; intpart; intpart--) *buf++ = DIG_MAX;
}

if ((to->frac = frac)) {
int lastdigits = frac % DIG_PER_DEC1;
for (frac /= DIG_PER_DEC1; frac; frac--) *buf++ = DIG_MAX;
if (lastdigits) *buf = frac_max[lastdigits - 1];
}
}

inline void max_my_decimal(my_decimal *to, int precision, int frac) {
assert((precision check_result(mask, result) & E_DEC_OVERFLOW) {
bool sign = val->sign();
val->sanity_check();
max_internal_decimal(val);
val->sign(sign);
}
/*
Avoid returning negative zero, cfr. decimal_cmp()
For result == E_DEC_DIV_ZERO *val has not been assigned.

*/
if (result != E_DEC_DIV_ZERO && val->sign() && decimal_is_zero(val))
val->sign(false);
return result;
}


## 超大常量数据生成的 DECIMAL 数据与 DECIMAL 字段类型的区别

1. DECIMAL 字段类型有显式的精度和小数位的限制，也就是 DECIMAL 字段插入数据时能插入的正数部分的长度为 M-D，而超大常量数据生成的 DECIMAL 数据则会隐含的优先处理考虑整数部分，整数部分处理完才继续处理小数部分，如果缓冲区不够则将小数位截断，如果缓冲区不够整数部分存放则转为 659
2. 在 MySQL 的服务源码中 DECIMAL 字段类型使用 Field_new_decimal 类型接收处理，而超大常量数据生成的 DECIMAL 数据由 Item_decimal 类型接收处理。

Enjoy GreatSQL 😃

GreatSQL MGR FAQ

## 关于 GreatSQL

GreatSQL是由万里数据库维护的MySQL分支，专注于提升MGR可靠性及性能，支持InnoDB并行查询特性，是适用于金融级应用的MySQL分支版本。

Original: https://www.cnblogs.com/greatsql/p/16615339.html
Author: GreatSQL
Title: DECIMAL 数据处理原理浅析

(0)

### 大家都在看

• #### MySQL优化之索引解析

索引的本质 MySQL索引或者说其他关系型数据库的索引的本质就只有一句话， 以空间换时间。 索引的作用 索引关系型数据库为了 加速对表中行数据检索的（ 磁盘存储的） 数据结构 索引…

数据库 2023年5月24日
0151
• #### 2022-08-20 数据库连接池

每次去初始化一个连接池，连接池中会有很多个连接等待被使用，使用完连接之后，不需要关闭连接，只需要把连接还回到连接池，还回到连接池的操作不需要我们手动控制。 数据库连接池负责分配、管…

数据库 2023年6月14日
0147
• #### 关于在vue项目中引入<el-img>标签失败的问题

问题如下 语法没有写错，但是冒号错了，不能在img后面紧接着冒号 只能在src前面紧接着冒号 Original: https://www.cnblogs.com/ly-heker/…

数据库 2023年6月11日
0135
• #### zabbix监控配置流程

zabbix监控配置流程 管理层次： 开发人员要加监控，需要让其提供监控指标运营人员要加监控，让其找开发要监控指标运维人员要加监控，让运营人员去找开发要监控指标。 配置层次: 1….

数据库 2023年6月14日
0144
• #### Python接口自动化——文件上传/下载接口

转载请注明出处❤️ 你好，我是测试蔡坨坨。 我们在做接口自动化测试的时候，经常会碰到 &#x6587;&#x4EF6;&#x4E0A;&#x4F20…

数据库 2023年6月11日
0135
• #### mysql常用操作汇总

工作中经常用会遇到这种情况，可以访问mysql所在的服务器，但是服务器端口不对外暴露（通常因为安全原因）。这时，操作数据库只能通过命令行和 mysql client窗口来实现。我对…

数据库 2023年5月24日
0138
• #### Mybatis基础知识大全！！！

1. 简介 1.1什么是Mybatis 1.2 如何获得Mybatis 1.3 使用Mybatis的好处： 2.初涉Mybatis 2.1环境搭建 2.2、创建一个模块（项目） 2…

数据库 2023年6月16日
0179
• #### Executor 创建线程

数据库 2023年6月9日
0147
• #### 事物的隔离性和MVCC

事物的隔离性 mysql的服务端是支持多个客户端同时与之连接的，每个客户端可能还并发了好几个连接，所以mysql是需要同时处理很多事情的，每一件独立的事情就叫做事务。我们知道事务有…

数据库 2023年5月24日
0143
• #### Python第二十二天 stat模块 os.chmod方法 os.stat方法 pwd grp模块 os.access()方法

Python第二十二天 stat模块 os.chmod方法 os.stat方法 pwd grp模块 os.access()方法 stat模块描述了os.stat(filename)…

数据库 2023年6月9日
0123
• #### 分布式事务解决方案

分布式事务解决方案 花开堪折直须折，莫待无花空折枝。 分布式事务是指事务的操作位于不同的节点上，需要保证事务的ACID特性。在分布式架构下，每个节点只知晓自身操作的成功与失败，无法…

数据库 2023年6月14日
0138
• #### Redis 哈希Hash底层数据结构

Redis 底层数据结构 Redis数据库就像是一个哈希表，首先对key进行哈希运算得到哈希值再取模得到一个下标，每个元素是一个节点，节点之间形成链表。这感觉有点像Java中的Ha…

数据库 2023年6月14日
0143
• #### mysql练习题emp,dept

DROP DATABASE IF EXISTS emp; CREATE DATABASE emp; USE emp; &#xA0;CREATE TABLE dept( &a…

数据库 2023年6月9日
0240
• #### MyBatisPlus代码生成示例

一、依赖 com.baomidou mybatis-plus-generator 3.5.3 org.projectlombok lombok 1.18.16 compile or…

数据库 2023年6月11日
0139
• #### Redis学习(1)—Redis概述

什么是NoSQL NoSQL：Not Only SQL，意思不仅仅是SQL，它是属于 非关系型数据库。那什么是关系型数据库？数据结构是一种有行有列的数据库。 NoSQL数据库是为了…

数据库 2023年6月14日
0137
• #### 10、比较Bigdecimal类型是否相等的方法

一、Bigdecimal.equals()详解： Bigdecimal的equals方法不仅仅比较值的大小是否相等，首先比较的是scale（scale是bigdecimal的保留小…

数据库 2023年6月6日
0153