专栏首页Sth interesting, as a DBA[转载] MySQL 浮点型的显示问题

[转载] MySQL 浮点型的显示问题

背景

我们打开MySQL客户端,执行下面的SQL语句:

drop table if exists t;
create table t(id double)engine=innodb;
insert into t values(1e-15),(1e-16);
select * from t;

出来的内容如下,我们看到浮点数1e-15用正常的数值来表示,1e-16用科学技术法来表示。

+-------------------+
| id                |
+-------------------+
| 0.000000000000001 |
|             1e-16 |
+-------------------+

我们知道在计算机中浮点数用来近似表示某个实数。浮点数有2种显示风格,一种是正常的表示(0.18, 2.345等),一种是科学技术法的表示(1.23e+12,2.45e-16等)。那么MySQL的浮点型在什么情况下表示成正常的实数(如0.18,2.345),什么情况下表示成科学计数法(如1.23e+12,2.45e-16)呢?下面我们进行更精确的实验以及从源码角度来解释MySQL对于浮点数的显示问题。

实验

我们用下面的SQL语句直接显示多个浮点数:

select (1e+14),(1e+15),(2.3e+14),(2.3e+15),(1e-15),(1e-16),(3.4e-15),(3.4e-16);

select出来的内容是:

+-----------------+-------+-----------------+---------+-------------------+-------+--------------------+---------+
| 1e+14           | 1e+15 | 2.3e+14         | 2.3e+15 | 1e-15             | 1e-16 | 3.4e-15            | 3.4e-16 |
+-----------------+-------+-----------------+---------+-------------------+-------+--------------------+---------+
| 100000000000000 |  1e15 | 230000000000000 |  2.3e15 | 0.000000000000001 | 1e-16 | 0.0000000000000034 | 3.4e-16 |
+-----------------+-------+-----------------+---------+-------------------+-------+--------------------+---------+

通过以上的例子再结合更多的实验我们可以看出这么一个规律:

  • 在数值大于0时,科学计数法表示的指数小于或等于14时,select出来的是正常非科学计数法的数值;
  • 在数值大于0时,科学计数法表示的指数大于14时,select出来的是科学计数法的数值;
  • 当数值小于0时,科学计数法表示的指数大于或等于-15时,select出来的是正常非科学计数法的数值;
  • 当数值小于0时,科学计数法表示的指数小于-15时,select出来的是科学计数法的数值。

另外由于上面的select并没有来自某个具体表,所以浮点数展示的规则是和存储引擎没有关系的,MySQL对于浮点数展示包装的逻辑是在server层完成的。

我们去代码里验证一下这个规律是否正确。

验证

我们可以用gdb跟到代码里面寻找这块逻辑,但是MySQL单单server层的代码也有好几万行,盲目的跟代码并不能很快的找到我们要找的位置。所以,跟代码前我们很有必要先分析一下这块逻辑会出现在什么位置。

我们知道MySQL对select的处理的大体过程是,客户端向服务端发送select,服务端解析select并把结果返回到客户端,那么这块逻辑就很有可能出现在服务端把结果送到客户端这个过程中。

最后通过跟踪代码我们发现了在MySQL将结果返回客户端的过程中,在下面这个位置的buffer->set_real对要显示的内容进行了包装,并把包装的结果放到buffer这个变量里。

sql/protocol.cc:
bool Protocol_text::store(double from, uint32 decimals, String *buffer)
{
#ifndef DBUG_OFF
  DBUG_ASSERT(field_types == 0 ||
	      field_types[field_pos] == MYSQL_TYPE_DOUBLE);
  field_pos++;
#endif
  buffer->set_real(from, decimals, thd->charset());
  return net_store_data((uchar*) buffer->ptr(), buffer->length());
}

在对set_real往更深的调用层次跟踪,我们找到了对浮点数的展示进行包装的位置:

strings/dtoa.c:
...
size_t my_gcvt(double x, my_gcvt_arg_type type, int width, char *to,
               my_bool *error)
...

通过分析my_gcvt这个函数,我们可以得出MySQL对于浮点数展示的规则。

首先我们必须知道以下这个事实(下面’f’format表示正常格式,’e’format表示科学计数法的格式):

MySQL对select出来的每一列占用的宽度是有要求的,如果浮点数在’f’format下的有效数字太多,就有可能超过最大宽度,这时若还想要用’f’format,就不得不丢失一些有效数字了。如果同样数值的’e’format不会丢失有效数字,MySQL就会把该浮点数从’f’format转为’e’format。

下面的这个if语句确定了用’f’format表示浮点数的条件。

strings/dtoa.c -> function my_gcvt

if ((have_space ||
    /*
      Not enough space, let's see if the 'f' format provides the most number
      of significant digits.
    */
     ((decpt <= width && (decpt >= -1 || (decpt == -2 &&
                                            (len > 1 || !force_e_format)))) &&
       !force_e_format)) &&

     /*
       Use the 'e' format in some cases even if we have enough space for the
       'f' one. See comment for MAX_DECPT_FOR_F_FORMAT.
     */
    (!have_space || (decpt >= -MAX_DECPT_FOR_F_FORMAT + 1 &&
                     (decpt <= MAX_DECPT_FOR_F_FORMAT || len > decpt))))

代码有点乱,但是通过看注释以及上下文,我们可以分析出用’f’format表示浮点数必须同时满足2个条件:

  1. 用’f’format表示浮点数不会因为宽度限制造成精度丢失。
  2. 浮点数用若用’e’format表示时的指数在一个临界值范围(-15,14)内,那么就用’f’format表示。

在前面的实验中,我们给出的几个浮点数若用’f’format并不会超过列的最大宽度,即满足条件1。那么这几个浮点数用’f’format还是’e’format表示就由条件2决定了,条件2和我们在实验中看到的规律相符。

原文链接:https://developer.aliyun.com/article/50941

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 浮点数加法引发的问题:浮点数的二进制表示

    1、问题: 之前有同学问过这样一个问题: echo|awk '{print 3.99 -1.19 -2.80}' 4.44089e-16 类似的问题还...

    用户1177713
  • double转换为int以及浮点型相加损失精度问题

    最近在做支付相关模块的业务,数据库字段却使用的是double类型,其实也行,只要计算不在sql语句中进行,也是没有问题的。

    陈灬大灬海
  • 谈谈MySQL如何选择float, double, decimal

    我们知道在MySQL中有3种类型可以表示实数,分别是float,double和decimal。关于如何合理得使用这三种类型,网上的答案也层出不穷。但是究竟该选择...

    麦洛
  • 隐秘的 MySQL 类型转换

    当索引字段 `phone` 为字符串类型时,字符串查询时候使用了索引`idx_phone`,而数值类型查询时候竟无法使用索引`idx_phone`。

    架构精进之路
  • 第四章《MySQL的数据类型和运算符》

    一、数据类型介绍: (1)数据表由多个字段组成,每一个字段都指定了自己的数据类型,指定了数据类型后,也就决定了向字段插入数据的内容; (2)不同的数据类型也...

    全栈程序员站长
  • c++ sprintf_s 和 sprintf 区别 double转换成string型(浮点数的格式化)(转载)

    https://www.cnblogs.com/finallyliuyu/archive/2010/08/27/1810071.html

    用户7886150
  • 数据库MySQL-数据类型

    整形支持显示宽度,显示宽带是最小的显示位数,如int(11)表示整形最少用11位表示,如果不够位数用0填充。显示宽度默认不起作用,必须结合zerofill才起作...

    cwl_java
  • Mysql系列 - 第2天:详解mysql数据类型(重点)

    上面表格中有符号和无符号写反了,[]包含的内容是可选的,默认是无符号类型的,无符号的需要在类型后面跟上unsigned

    路人甲Java
  • Mysql系列 - 第2天:详解mysql数据类型(重点)

    上面表格中有符号和无符号写反了,[]包含的内容是可选的,默认是无符号类型的,无符号的需要在类型后面跟上unsigned

    公众号guangcity
  • MySQL数据类型与优化

    1、假如只需要存0~255之间的数,无负数,应使用tinyint unsigned(保证最小数据类型) 2、如果长度不可定,如varchar,应该选择一个你认...

    Java架构师必看
  • 数据库之数据类型详解

    查看表的详细信息如下(在创建表的时候没有指定其长度,但是每一列都有自己默认的长度):

    小手冰凉
  • MySQL数字类型学习笔记

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    SmileNicky
  • mysql学习总结02 — 数据类型

    无符号:表示存储的数据在当前字段中,没有负数(只有正数,例如 tinyint 区间为 0~255)

    CS逍遥剑仙
  • MySQL(四)之MySQL数据类型

    一、数据类型概述  MySQL的数据类型有大概可以分为5种,分别是 整数类型、浮点数类型和定点数类型、日期和时间类型、字符串类型、二进制类型。现在可以来看看你对...

    用户1195962
  • three.js 加载透明贴图模型不正常显示的问题,渲染透明贴图gltf模型

    首先看一下在https://gltf-viewer.donmccurdy.com/上预览的效果,直接上图:

    acoolgiser
  • MySQL文档阅读(一)-数字类型

    MySQL支持很多系列的SQL数据类型:数字类型(numeric types)、日期和时间类型(date and time types)、字符串类型(字符和字节...

    阿杜
  • 谈谈 MySQL 隐式类型转换

    来源:andyqian www.andyqian.com/2017/11/11/database/MySQLConvert/ 前言 今天我们继续回到MySQL系...

    企鹅号小编
  • MySQL学习笔记

    这些类型包括严格数值数据类型(INTEGER、SMALLINT、DECIMAL和NUMERIC),以及近似数值数据类型(FLOAT、REAL和DOUBLE PR...

    林祈
  • 去 BAT 面试,总结了这 50 道 MySQL 面试题!

    CHAR_LENGTH是字符数,而LENGTH是字节数。Latin字符的这两个数据是相同的,但是对于Unicode和其他编码,它们是不同的。

    田维常

扫码关注云+社区

领取腾讯云代金券