浮点类型 (FLOAT 和 DOUBLE)
描述
Doris 提供了两种浮点数据类型:FLOAT 和 DOUBLE。这些是可变精度的数值类型,遵循 IEEE 754 浮点算术标准。
| 类型 | 别名 | 存储空间 | 描述 |
|---|---|---|---|
| FLOAT | FLOAT4, REAL | 4 字节 | 单精度浮点数 |
| DOUBLE | FLOAT8, DOUBLE PRECISION | 8 字节 | 双精度浮点数 |
取值范围
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 标准:
Infinity或Inf:正无穷大-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:
| 表达式 | PostgreSQL | IEEE 754 | Doris |
|---|---|---|---|
| 1.0 / 0.0 | 错误 | Infinity | NULL |
| 0.0 / 0.0 | 错误 | NaN | NULL |
| -1.0 / 0.0 | 错误 | -Infinity | NULL |
| 'Infinity' / 'Infinity' | NaN | NaN | NaN |
| 1.0 / 'Infinity' | 0.0 | 0.0 | 0 |
| 'Infinity' - 'Infinity' | NaN | NaN | NaN |
| 'Infinity' - 1.0 | Infinity | Infinity | Infinity |
比较运算
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; - 正负零区分输出(
-0与0); NaN、Infinity、-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 之前的版本按固定有效位数输出(FLOAT 约 7 位、DOUBLE 约 16 位有效数字),可能出现"输出的字符串再解析回来与原值不同"的情况。4.1.4 改为最短往返表示后,存储的数值没有任何变化,只是文本形式可能不同,例如:
| 4.1.4 之前 | 4.1.4 及之后 |
|---|---|
246.9120025634766 | 246.91200256347656 |
15241.38425247656 | 15241.384252476564 |
11.11107561317707 | 11.111075613177073 |
DOUBLE 值 2.0 在部分文本输出中为 2.0 | 输出为 2 |
0.0000123456 | 1.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)的浮点列。
如果下游系统对浮点数的字符串形式做了精确匹配(例如比对导出文件),升级后需要相应调整。
最佳实践
-
选择合适的数据类型:对于财务计算或其他需要精确数值的场景,应使用
DECIMAL类型而非浮点类型。 -
谨慎进行相等比较:避免直接比较两个浮点值是否相等,尤其是在 JOIN 操作中。
-
小心处理字符串转换:将浮点数转换为字符串再转回浮点数可能会引入额外的精度损失。
-
了解平台差异:不同的数据库系统在处理浮点运算时可能存在细微差别,特别是在处理 NaN 和 Infinity 等特殊情况时(不过大多数数据库系统都基本遵循 IEEE 标准)。
-
展示结果时进行适当舍入:在展示浮点计算结果时,考虑进行适当的舍入处理,以减少精度问题对用户的困扰。
关键字
FLOAT, FLOAT4, REAL, DOUBLE, DOUBLE PRECISION, FLOAT8, 浮点