dws的除法

举报
dgt 发表于 2024/01/23 10:17:12 2024/01/23
【摘要】 有客户提出疑问,在dws里执行select 1.0/2,时结果为0.50000000000000000000,结果中的后面0太多,对业务有影响。

有客户提出疑问,在dws里执行select 1.0/2,时结果为0.50000000000000000000,结果中的后面0太多,对业务有影响。
下面解析下出现该情况的原因。

首先我们建立个测试用例
create table divtest (data_int int,data_float float8,data_numeric numeric);
insert into divtest values(1,1,1);
postgres=# select data_int/2,data_float/2,data_numeric/2 from divtest;
 ?column? | ?column? |        ?column?
----------+----------+------------------------
        0 |      0.5 | 0.50000000000000000000
从上面的现象可以看出,这里是把select 1.0/2看做是numeric的除法了。
这里参考下postgres的官方文档描述:如果一个不包含小数点和指数的数字常量的值适合类型integer(32 位),它首先被假定为类型integer。否则如果它的值适合类型bigint(64 位),它被假定为类型bigint。再否则它会被取做类型numeric。包含小数点和/或指数的常量总是首先被假定为类型numeric。【1】

查询pg_operator表,也可以用\do查询
postgres=# \doS+ /
                                                     List of operators
   Schema   | Name |  Left arg type   |  Right arg type  |   Result type    |   Function    |         Description
------------+------+------------------+------------------+------------------+---------------+------------------------------
 pg_catalog | /    | bigint           | bigint           | bigint           | int8div       | divide
 pg_catalog | /    | bigint           | integer          | bigint           | int84div      | divide
 pg_catalog | /    | bigint           | smallint         | bigint           | int82div      | divide
 pg_catalog | /    | box              | point            | box              | box_div       | divide box by point (scale)
 pg_catalog | /    | circle           | point            | circle           | circle_div_pt | divide
 pg_catalog | /    | double precision | double precision | double precision | float8div     | divide
 pg_catalog | /    | double precision | real             | double precision | float84div    | divide
 pg_catalog | /    | integer          | bigint           | bigint           | int48div      | divide
 pg_catalog | /    | integer          | integer          | integer          | int4div       | divide
 pg_catalog | /    | integer          | smallint         | integer          | int42div      | divide
 pg_catalog | /    | interval         | double precision | interval         | interval_div  | divide
 pg_catalog | /    | money            | bigint           | money            | cash_div_int8 | divide
 pg_catalog | /    | money            | double precision | money            | cash_div_flt8 | divide
 pg_catalog | /    | money            | integer          | money            | cash_div_int4 | divide
 pg_catalog | /    | money            | money            | double precision | cash_div_cash | divide
 pg_catalog | /    | money            | real             | money            | cash_div_flt4 | divide
 pg_catalog | /    | money            | smallint         | money            | cash_div_int2 | divide
 pg_catalog | /    | numeric          | numeric          | numeric          | numeric_div   | divide
 pg_catalog | /    | path             | point            | path             | path_div_pt   | divide (rotate/scale path)
 pg_catalog | /    | point            | point            | point            | point_div     | divide points (scale/rotate)
 pg_catalog | /    | real             | double precision | double precision | float48div    | divide
 pg_catalog | /    | real             | real             | real             | float4div     | divide
 pg_catalog | /    | smallint         | bigint           | bigint           | int28div      | divide
 pg_catalog | /    | smallint         | integer          | integer          | int24div      | divide
 pg_catalog | /    | smallint         | smallint         | smallint         | int2div       | divide
(25 rows)

在操作符匹配中,会根据以上规则匹配,若匹配不上,则会进行类型转换再匹配,若仍无法匹配则会报错。
综合上述
select 1/2 当1为int时,smallint/smallint,使用int2div,返回smalint,整数除法结果0
这里跳过float除法在后面解释
select 1/2 当1为numeric时,numeric/numeric,numeric_div,返回numeric除法结果0.50000000000000000000。

select 1.0/2会看成为select 1.0::numeric/2::numeric,再通过使用的/的函数是numeric_div,
可以查询pg_proc表,也可以使用\sf查询定义
postgres=# \sf+ numeric_div
        CREATE OR REPLACE FUNCTION pg_catalog.numeric_div(numeric, numeric)
         RETURNS numeric
         LANGUAGE internal
         IMMUTABLE PARALLEL SAFE STRICT
1       AS $function$numeric_div$function$
结果会返回类型为numeric的,该值为精确值,并保留了一定的数字长度。

用下面示例可以确认numeric_div返回精确值,而float8div浮点数非精确值。
postgres=#  select data_int/6,data_float/6,data_numeric/6 from divtest;
 ?column? |      ?column?       |        ?column?
----------+---------------------+------------------------
        0 | 0.16666666666666666 | 0.16666666666666666667
postgres=#  select data_int/6,data_float/6=0.16666666666666666,data_numeric/6=0.16666666666666666667 from divtest;
 ?column? | ?column? | ?column?
----------+----------+----------
        0 | t        | t
(1 row)
postgres=#  select data_int/6,data_float/6=0.16666666666666667,data_numeric/6=0.16666666666666666666 from divtest;
 ?column? | ?column? | ?column?
----------+----------+----------
        0 | t        | f
(1 row)
可以看到numeric_div的返回值只能和0.16666666666666666667相等,是精确的,而浮点数和2个值都相等。

让我们再回到浮点数除法。
select 1/2 当1为float8时,double precision/double precision即float8/float8,使用float8div,返回double precision,浮点数除法结果默认15位有效位,即0.500000000000000。
又由于浮点数显示受extra_float_digits控制,在默认值0的情况下,浮点值以其最短精确的十进制表示的文本形式输出0.5。
注;dws基于pg9.2,extra_float_digits在pg不同版本有些许差别,这里不展开,具体差别可以查询pg相关文档。

最后,回到客户疑问,我们可以通过指定数据类型或者类型转换来解决
postgres=# select 1.0::float/2;
 float8
--------
    0.5
(1 row)

注【1】:http://postgres.cn/docs/12/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。