为什么从双精度到数字的转换会四舍五入到 15 位有效数字?

Erw*_*ter 9 postgresql floating-point cast

double precision从( float8) 到numeric四舍五入到 15 个有效小数位的转换,从而丢失信息。显然,更高的精度是可能的。转换为bigint(对于其范围内的值)可以保留更高的精度:

SELECT f8          AS float8
     , f8::bigint  AS to_bigint
     , f8::numeric AS to_numeric
FROM  (
   VALUES
     ('8217316934885843456'::float8)
   , ('8217316934885843457')
   , ('8217316934885844479')
   , ('8217316934885844480')   
   , ('8217316934885845503')
   , ('8217316934885845584')   
   ) t(f8);

        float8         |      to_bigint      |     to_numeric      
-----------------------+---------------------+---------------------
 8.217316934885843e+18 | 8217316934885842944 | 8217316934885840000
 8.217316934885844e+18 | 8217316934885843968 | 8217316934885840000
 8.217316934885844e+18 | 8217316934885843968 | 8217316934885840000
 8.217316934885845e+18 | 8217316934885844992 | 8217316934885840000
 8.217316934885845e+18 | 8217316934885844992 | 8217316934885840000
 8.217316934885846e+18 | 8217316934885846016 | 8217316934885850000
(6 rows)
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

我在稍微翻转的边界处选择了值float8- 至少在我的本地安装(Postgres 13、Ubuntu、Intel CPU)、dbfiddle 以及 AWS 上的托管数据库中是这样。

当前手册(Postgres 14)中的一些相关引用:

数字类型概述中

double precision... 15 位小数精度

浮点数章节中:

double precision类型的范围约为 至1E-3071E+308精度至少为 15 位

和:

默认情况下,浮点值以其最短的精确十进制表示形式以文本形式输出;产生的十进制值比以相同二进制精度表示的任何其他值更接近真实存储的二进制值。(但是,输出值目前永远不会恰好位于两个可表示值之间的中间位置,以避免输入例程未正确遵守舍入到最接近偶数规则的广泛错误。)该值最多使用 17 位有效小数数值位数float8,最多 9 位数值float4

大胆强调我的。

那么为什么不在转换为 时保留最多 17 位有效十进制数字numeric呢?演员阵容bigint做得更好!

这具有反直觉的效果(至少对我来说)。强制转换 tonumeric会失去精度,而强制转换 to 则bigint不会。

test=> SELECT '8217316934885843456'::float8 = '8217316934885843456'::float8::bigint::float8
test->      , '8217316934885843456'::float8 = '8217316934885843456'::float8::numeric::float8;
 ?column? | ?column? 
----------+----------
 t        | f
Run Code Online (Sandbox Code Playgroud)

这是一件出了名的棘手的事情。那么也许有我没有看到的将上限限制在 15 位数字的充分理由?
或者 Postgres 可以做得更好吗?

小智 2

无论如何,Bigint 会更精确(最多 19 位数字)。如果我们假设value 设置为 0,则观察到的float8和之间的转换行为numeric是有意义的;extra_float_digits

SET extra_float_digits = 0;
SELECT '8217316934885843456'::numeric::text
UNION ALL
SELECT '8217316934885843456'::float8::text
UNION ALL
SELECT '8217316934885843456'::float8::numeric::text
Run Code Online (Sandbox Code Playgroud)

结果:

8217316934885843456
8.21731693488584e+18
8217316934885840000
Run Code Online (Sandbox Code Playgroud)

如果增加extra_float_digits(默认值为 1,但最多可以增加到 3),转换为 float8 的精度会发生变化(但不超过 1 位),但从 float8 转换为 numeric 的精度不会改变。这可能意味着 Postgres 代码中的某个地方它使用“旧”精度逻辑来进行此转换。这可能是一个错误或疏忽,但可能有真正的原因(不幸的是我不知道)。

为了与旧版本 PostgreSQL 生成的输出兼容,并允许降低输出精度,可以使用 extra_float_digits 参数来选择舍入十进制输出。将值设置为 0 会恢复之前将值舍入为 6(对于 float4)或 15(对于 float8)有效十进制数字的默认设置。