关于 PostgreSQL 的 round() 函数讨论

这是 PostgreSQL Advent Calendar 2019 第13天的文章。

这次我们将讨论PostgreSQL提供的round函数。在PostgreSQL的日本语文档中,round()被解释为”取最接近的整数”,它经常被用来进行数值的四舍五入。实际上,我们可以在PostgreSQL 12的版本中验证,在给round()传递从1.0到3.0的NUMERIC类型的值,以0.1为间隔时可以观察到以下的四舍五入结果。

=# SELECT num, round(num::NUMERIC) FROM generate_series(1.0, 3.0, 0.1) num;
 num | round 
-----+-------
 1.0 |     1
 1.1 |     1
 1.2 |     1
 1.3 |     1
 1.4 |     1
 1.5 |     2
 1.6 |     2
 1.7 |     2
 1.8 |     2
 1.9 |     2
 2.0 |     2
 2.1 |     2
 2.2 |     2
 2.3 |     2
 2.4 |     2
 2.5 |     3
 2.6 |     3
 2.7 |     3
 2.8 |     3
 2.9 |     3
 3.0 |     3
(21 rows)

接下来,当将DOUBLE PRECISION类型的数值从1.0到3.0以0.1的精度传入round()函数时,结果如下所示。

=# SELECT num, round(num::DOUBLE PRECISION) FROM generate_series(1.0, 3.0, 0.1) num;
 num | round 
-----+-------
 1.0 |     1
 1.1 |     1
 1.2 |     1
 1.3 |     1
 1.4 |     1
 1.5 |     2
 1.6 |     2
 1.7 |     2
 1.8 |     2
 1.9 |     2
 2.0 |     2
 2.1 |     2
 2.2 |     2
 2.3 |     2
 2.4 |     2
 2.5 |     2
 2.6 |     3
 2.7 |     3
 2.8 |     3
 2.9 |     3
 3.0 |     3
(21 rows)

看到这些round()的执行结果,有没有什么引起你关注的地方吗?

实际上,当round(2.5)的结果时,参数2.5为NUMERIC类型时为3,为DOUBLE PRECISION类型时为2,这两者结果是不同的。将2.5四舍五入为3是常规的四舍五入,而将其四舍五入为2是所谓的银行舍入(向偶数舍入,五舍五入)。也就是说,在PostgreSQL中,round()的行为根据参数的数据类型而异,round(NUMERIC)进行常规四舍五入,而round(DOUBLE PRECISION)则进行银行舍入。

当我们将1.5、2.5、3.5等数字分别以NUMERIC型和DOUBLE PRECISION型传递给round()函数时,可以清楚地看到结果的差异。

=# SELECT num, round(num::NUMERIC) "NUMERIC", round(num::DOUBLE PRECISION) "DOUBLE" FROM generate_series(1.5, 10.5, 1.0) num;
 num  | NUMERIC | DOUBLE 
------+---------+--------
  1.5 |       2 |      2
  2.5 |       3 |      2
  3.5 |       4 |      4
  4.5 |       5 |      4
  5.5 |       6 |      6
  6.5 |       7 |      6
  7.5 |       8 |      8
  8.5 |       9 |      8
  9.5 |      10 |     10
 10.5 |      11 |     10
(10 rows)

请注意,上述所述的PostgreSQL提供的round函数的行为取决于参数的数据类型,因此在使用时需要注意。如果您在现有系统中使用round函数,请确认是否得到了预期的执行结果。

附加内容:round()函数的实现

在中国 Postgres 中调用round(NUMERIC)会使用内部函数numeric_round执行四舍五入逻辑的实现。而round(DOUBLE PRECISION)会调用内部函数dround,在该函数内仅执行 rint()。

rint()是一个将值四舍五入到整数的库调用。通过库调用fesetround()可以改变rint()的舍入模式。由于PostgreSQL没有通过fesetround()改变舍入模式,所以rint()遵循默认的舍入模式1,即银行舍入,来进行值的舍入。

附赠内容:参数设定时的四舍五入。

如果在设置PostgreSQL参数(例如log_min_duration_statement)时指定带有小数点的值,该值将通过rint()进行四舍五入。换句话说,银行舍入结果的整数将成为参数的设定值。

=# SET log_min_duration_statement TO 10.5;

=# SHOW log_min_duration_statement;
 log_min_duration_statement 
----------------------------
 10ms
(1 row)
根据操作系统和环境的不同,可能默认的四舍五入模式不是银行家舍入。
广告
将在 10 秒后关闭
bannerAds