【openGauss】OPENGAUSS/POSTGRESQL 中float类型到int类型的隐式转换

下面这条sql在oracle和POSTGRESQL/OPENGAUSS中的查询结果不一致

sql 复制代码
select  cast(cast(0.5 as float) as integer);

在oracle中返回1,在openGauss中返回0,咋一看好像是openGauss中使用了截断的方式,但是如果执行

sql 复制代码
select  cast(cast(1.5 as float) as integer);

则会发现oracle和openGauss中同样返回了2,这个时候就会猜想是不是这个float类型在两个数据库中实现并不一样导致的,因为ORACLE的float是按number格式存储的,不是标准定义中的浮点类型,而是一种精确类型;而openGauss中的float符合标准定义的浮点类型,是一种非精确类型。

但我们并不能妄下这种判断,多测几组数据:

sql 复制代码
select 
 cast(cast(0.5 as float) as integer) c1 ,
 cast(cast(0.6 as float) as integer) c2 ,
 cast(cast(1.5 as float) as integer) c3,
 cast(cast(2.5 as float) as integer) c4,
 cast(cast(3.5 as float) as integer) c5, 
 cast(cast(4.5 as float) as integer) c6
c1 c2 c3 c4 c5 c6
0 1 2 2 4 4

从这组数据中发现一个规律,当小数尾数为5进行舍入时,前一位总是偶数,这自然联想到了另一种四舍五入算法,即银行家算法,对应ORACLE12C新增的一个函数round_ties_to_even。

openGauss数据库是开源的,我们完全可以通过查看源码来验证我们的猜想。

接下来开始就是说明如何通过这种问题来找到对应的源码逻辑。

首先要知道一些openGauss的元数据基础知识。

openGauss的元数据基本继承postgresql,所以这个知识可以复用。

在postgresql中,几乎所有的类型、函数、操作符、规则等,都可以在元数据表中查到。对于类型转换,即cast,也有一张对应的表,即pg_cast,下面这个SQL就是查询从float8到int8是否存在直接的类型转换规则

sql 复制代码
select * from pg_cast h 
where h.castsource ='float8'::regtype 
and h.casttarget ='int8'::regtype;
castsource casttarget castfunc castcontext castmethod castowner
701 20 483 i f

查到了是有的,这里castmethod为f,表示这个转换是使用某个函数进行转换的,一般是二进制存储结构有变更才会需要使用到函数。然后castfunc即为这个转换函数的oid,接下来我们去查这是用的哪个函数

sql 复制代码
select proname,prosrc from pg_proc where oid=483;
proname prosrc
int8 dtoi8

可以看到使用的是int8函数,然后int8的函数源码是dtoi8,此时我们就可以去openGauss源码中搜索dtoi8了,

找到了

.\openGauss-server\src\common\backend\utils\adt\int8.cpp

c 复制代码
/* dtoi8()
 * Convert float8 to 8-byte integer.
 */
Datum dtoi8(PG_FUNCTION_ARGS)
{
    float8 num = PG_GETARG_FLOAT8(0);

    /*
     * Get rid of any fractional part in the input.  This is so we don't fail
     * on just-out-of-range values that would round into range.  Note
     * assumption that rint() will pass through a NaN or Inf unchanged.
     */
    num = rint(num);

    /*
     * Range check.  We must be careful here that the boundary values are
     * expressed exactly in the float domain.  We expect PG_INT64_MIN  to be an
     * exact power of 2, so it will be represented exactly; but PG_INT64_MAX
     * isn't, and might get rounded off, so avoid using it.
     */
    if (num < (float8)PG_INT64_MIN || num >= -((float8)PG_INT64_MIN) || isnan(num)) {
        if (fcinfo->can_ignore && !isnan(num)) {
            ereport(WARNING, (errmsg("bigint out of range")));
            PG_RETURN_INT64(num < (float8)PG_INT64_MIN ? LONG_MIN : LONG_MAX);
        }
        ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("bigint out of range")));
    }

    PG_RETURN_INT64((int64)num);
}

这段代码注释加警告比实际逻辑还多,但真正要看的只有 num = rint(num);这一行,这里又引用到了rint函数,点进去

c 复制代码
double rint(double x)
{
    return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5);
}

如果不是开发人员,对这种计算看不懂,可以借助AI

这段代码定义了一个名为 rint 的函数,用于将一个双精度浮点数 x 四舍五入到最接近的整数。具体来说:

如果 x 大于或等于 0.0,则返回 floor(x + 0.5),即将 x 加上 0.5 后向下取整。

如果 x 小于 0.0,则返回 ceil(x - 0.5),即将 x 减去 0.5 后向上取整。

这样可以实现对正数和负数的四舍五入。

这样就否定了之前的猜想,这里是按照常规的算法进行四舍五入的,但结果竟然和银行家算法一样。

于是我们回到最初的猜想,这个问题仍然与不精确的浮点类型有关,其实ORACLE也有这种浮点类型,叫binary_float和binary_double,这两个类型不能在sql中使用,只能在plsql中使用,我们写个简单的例子在ORACLE进行测试

sql 复制代码
declare
x binary_integer;
begin
select  cast(0.5 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(0.6 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(1.5 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(2.5 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(3.5 as binary_double) into x from dual;
dbms_output.put_line(x);
end;

0

0

1

2

3

竟然出现了第三种结果,即全部都是按trunc

再把binary_integer改成integer,再在ORACLE中测试

sql 复制代码
declare
x integer;
begin
select  cast(0.5 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(0.6 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(1.5 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(2.5 as binary_double) into x from dual;
dbms_output.put_line(x);
select  cast(3.5 as binary_double) into x from dual;
dbms_output.put_line(x);
end;

.5

.59999999999999998

1.5

2.5

3.5

出现了第四种结果!ORACLE的integer竟然能显示出小数!

我们理解不同的数据类型可以有不同的表现行为,也知道oracle中的integer其实是按number类型进行的实现,但是这明晃晃的声明了integer类型竟然还能打印出小数,这对于应用开发者来说如何能够接受?

当然,我们可以认为这个用例的用法是不合理的,正常情况下小数转换成整数应该使用函数来处理,而不是通过类型转换。也可能之前从来没有人这么用过,所以ORACLE一直没发现存在这个问题。

于是回到最开始的问题上,openGauss/postgresql和ORACLE表现不一致,究竟谁是对的,或许我们永远都无法找到准确答案,这只能算是一种行为差异,并且都可以解释得通。但在看到ORACLE换个方式出现了第三种结果,甚至还出现了整数类型显示小数这种奇葩BUG时,ORACLE的权威性陡然下降。

为了避免这种问题,建议无论在用什么数据库时,对于类型转换,还是慎重一些为好

相关推荐
幽兰的天空29 分钟前
python实现excel数据导入数据库
数据库
尘佑不尘2 小时前
shodan5,参数使用,批量查找Mongodb未授权登录,jenkins批量挖掘
数据库·笔记·mongodb·web安全·jenkins·1024程序员节
传输能手2 小时前
从三方云服务器将数据迁移至本地,如何保障安全高效?
大数据·服务器·数据库
BinTools图尔兹2 小时前
CQ社区版 v2024.10 | 支持k8s、helm部署!
数据库·安全·k8s·helm·数据安全·数据库管理员
北笙··3 小时前
Redis慢查询分析优化
数据库·redis·缓存
p-knowledge3 小时前
redis的三种客户端
数据库·redis·缓存
积水成江3 小时前
Redis相关面试题
数据库·redis·缓存
bigcarp3 小时前
Django ORM 数据库管理 提高查询、更新性能的技巧和编程习惯:
数据库·python·django
Zilliz Planet4 小时前
GenAI 生态系统现状:不止大语言模型和向量数据库
数据库·人工智能·语言模型·自然语言处理
瓜牛_gn5 小时前
redis详细教程(4.GEO,bitfield,Stream)
数据库·redis·缓存