关于 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)