跳到主要内容
最后 更新

浮点类型 (FLOAT 和 DOUBLE)

描述

Doris 提供了两种浮点数据类型:FLOATDOUBLE。这些是可变精度的数值类型,遵循 IEEE 754 浮点算术标准。

类型别名存储空间描述
FLOATFLOAT4, REAL4 字节单精度浮点数
DOUBLEFLOAT8, DOUBLE PRECISION8 字节双精度浮点数

取值范围

FLOAT

Doris 使用 IEEE-754 单精度浮点数,取值范围为:

  • -∞ (-Infinity)
  • [-3.402E+38, -1.175E-37]
  • 0
  • [1.175E-37, 3.402E+38]
  • +∞ (+Infinity)
  • NaN (不是数字)

详情请参阅 C++ float 类型Wikipedia 单精度浮点格式

DOUBLE

Doris 使用 IEEE-754 双精度浮点数,取值范围为:

  • -∞ (-Infinity)
  • [-1.79769E+308, -2.225E-307]
  • 0
  • [+2.225E-307, +1.79769E+308]
  • +∞ (+Infinity)
  • NaN (不是数字)

详情请参阅 C++ double 类型Wikipedia 双精度浮点格式

特殊值

除了普通的数值外,浮点类型还有几个特殊值,这些值符合 IEEE 754 标准:

  • InfinityInf:正无穷大
  • -Infinity-Inf:负无穷大
  • NaN:不是数字(Not a Number)

可以通过 CAST 转换来生成这些特殊值:

mysql> select cast('NaN' as double), cast('inf' as double), cast('-Infinity' as double);
+-----------------------+-----------------------+-----------------------------+
| cast('NaN' as double) | cast('inf' as double) | cast('-Infinity' as double) |
+-----------------------+-----------------------+-----------------------------+
| NaN | Infinity | -Infinity |
+-----------------------+-----------------------+-----------------------------+

浮点数还有一个不太直观的特性:存在两种不同的零值,即 +0-0。 虽然它们在大多数情况下被视为相等,但它们的符号位不同:

mysql> select cast('+0.0' as double), cast('-0.0' as double);
+------------------------+------------------------+
| cast('+0.0' as double) | cast('-0.0' as double) |
+------------------------+------------------------+
| 0 | -0 |
+------------------------+------------------------+

浮点数运算

算术运算

Doris 的浮点数支持常见的加减乘除等算术运算。

需要特别注意的是,Doris 在处理浮点数除以 0 的情况时,并不完全遵循 IEEE 754 标准。

Doris 在这方面参考了 PostgreSQL 的实现,当除以 0 时不会生成特殊值,而是返回 SQL NULL:

表达式PostgreSQLIEEE 754Doris
1.0 / 0.0错误InfinityNULL
0.0 / 0.0错误NaNNULL
-1.0 / 0.0错误-InfinityNULL
'Infinity' / 'Infinity'NaNNaNNaN
1.0 / 'Infinity'0.00.00
'Infinity' - 'Infinity'NaNNaNNaN
'Infinity' - 1.0InfinityInfinityInfinity

比较运算

IEEE 标准定义的浮点数比较与通常的整数比较有一些重要区别。例如,负零和正零被视为相等,而任何 NaN 值与任何其他值(包括它自身)比较时都不相等。所有有限浮点数都严格小于 +∞,严格大于 -∞。

为了确保结果的一致性和可预测性,Doris 对 NaN 的处理与 IEEE 标准有所不同。 在 Doris 中,NaN 被视为大于所有其他值(包括 Infinity), NaN 等于 NaN。

mysql> select * from sort_float order by d;
+------+-----------+
| id | d |
+------+-----------+
| 5 | -Infinity |
| 2 | -123 |
| 1 | 123 |
| 4 | Infinity |
| 8 | NaN |
| 9 | NaN |
+------+-----------+

mysql> select
cast('Nan' as double) = cast('Nan' as double) ,
cast('Nan' as double) > cast('Inf' as double) ,
cast('Nan' as double) > cast('123456.789' as double);
+-----------------------------------------------+-----------------------------------------------+------------------------------------------------------+
| cast('Nan' as double) = cast('Nan' as double) | cast('Nan' as double) > cast('Inf' as double) | cast('Nan' as double) > cast('123456.789' as double) |
+-----------------------------------------------+-----------------------------------------------+------------------------------------------------------+
| 1 | 1 | 1 |
+-----------------------------------------------+-----------------------------------------------+------------------------------------------------------+

浮点精度问题

近似值和精度损失

浮点数本质上是一种近似表示形式。这意味着某些十进制值无法在浮点数的二进制表示中被精确存储,只能以近似值的方式保存。因此,在存储和检索过程中可能会出现微小的差异。

例如:

mysql> SELECT CAST(1.3 AS FLOAT) - CAST(0.7 AS FLOAT) = CAST(0.6 AS FLOAT);
+--------------------------------------------------------------+
| CAST(1.3 AS FLOAT) - CAST(0.7 AS FLOAT) = CAST(0.6 AS FLOAT) |
+--------------------------------------------------------------+
| 0 |
+--------------------------------------------------------------+

由于浮点表示误差,这可能不会按预期评估为 TRUE

运算不满足结合律

由于浮点运算中的精度限制,浮点数的计算特性与理论数学运算有所差异。浮点加法和乘法不严格遵循结合律和分配律

这就导致了一个重要后果:计算顺序的不同可能会产生略微不同的结果。 由于 Doris 采用 MPP 架构,无法保证数据的精确处理顺序,因此即使输入数据完全相同,涉及浮点数的计算可能在不同执行中产生轻微不同的结果。

聚合函数

对浮点值执行聚合函数可能会积累误差,特别是在处理大规模数据集时。当数据中包含极大或极小的值时,这种误差会被进一步放大。 由于计算顺序不确定,当数据中存在极端值时,多次执行相同的聚合函数可能会得到不同的结果。

Join 操作

与聚合函数类似,不建议在浮点列上进行表连接操作。由于浮点数的精度问题,两个理论上相等的值可能在内部表示上略有差异,从而导致匹配失败。

浮点数的输出

4.1.4 版本起,浮点数转换为字符串时,Doris 输出能够无损还原(Round-Trip)该浮点值的最短字符串:即再把这个字符串解析回 FLOAT / DOUBLE,可以得到完全相同的二进制值,并且不会输出多余的位数。

输出格式规则:

  • 十进制指数落在 [-4, 16) 区间时使用普通定点表示;
  • 否则使用科学计数法,指数部分使用小写 e、带显式符号、且至少两位数字,例如 1.23456e-05
  • 正负零区分输出(-00);
  • NaNInfinity-Infinity 的写法保持不变。
mysql> select cast('0.000123456' as double), cast('0.0000123456' as double);
+-------------------------------+--------------------------------+
| cast('0.000123456' as double) | cast('0.0000123456' as double) |
+-------------------------------+--------------------------------+
| 0.000123456 | 1.23456e-05 |
+-------------------------------+--------------------------------+
版本行为变更(4.1.4)

4.1.4 之前的版本按固定有效位数输出(FLOAT 约 7 位、DOUBLE 约 16 位有效数字),可能出现"输出的字符串再解析回来与原值不同"的情况。4.1.4 改为最短往返表示后,存储的数值没有任何变化,只是文本形式可能不同,例如:

4.1.4 之前4.1.4 及之后
246.9120025634766246.91200256347656
15241.3842524765615241.384252476564
11.1110756131770711.111075613177073
DOUBLE 值 2.0 在部分文本输出中为 2.0输出为 2
0.00001234561.23456e-05

影响范围较广,包括:MySQL 协议查询结果、CAST(... AS STRING)、ARRAY / MAP / STRUCT 元素展示、JSON / VARIANT 展示、SELECT INTO OUTFILE 的 CSV / Parquet / ORC 文本输出、EXPORT、Stream Load 返回值,以及外表(Hive / Iceberg / Paimon / JDBC / ES / TVF)的浮点列。

如果下游系统对浮点数的字符串形式做了精确匹配(例如比对导出文件),升级后需要相应调整。

最佳实践

  1. 选择合适的数据类型:对于财务计算或其他需要精确数值的场景,应使用 DECIMAL 类型而非浮点类型。

  2. 谨慎进行相等比较:避免直接比较两个浮点值是否相等,尤其是在 JOIN 操作中。

  3. 小心处理字符串转换:将浮点数转换为字符串再转回浮点数可能会引入额外的精度损失。

  4. 了解平台差异:不同的数据库系统在处理浮点运算时可能存在细微差别,特别是在处理 NaN 和 Infinity 等特殊情况时(不过大多数数据库系统都基本遵循 IEEE 标准)。

  5. 展示结果时进行适当舍入:在展示浮点计算结果时,考虑进行适当的舍入处理,以减少精度问题对用户的困扰。

关键字

FLOAT, FLOAT4, REAL, DOUBLE, DOUBLE PRECISION, FLOAT8, 浮点