From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
Date: Tue, 11 Aug 2026 19:53:33 +0500
Subject: [PATCH] Fix covar_pop/regr_sxy for Inf with a constant other input

Commit 6498287696d tracks commonX/commonY so that Sxx, Syy, and Sxy
stay exactly zero while a column is still constant.  That fixed
roundoff problems in corr() and friends.  It also skipped the
Youngs-Cramer update of Sxy whenever either side was still constant.

If the other argument later becomes Inf or NaN, the Youngs-Cramer
update we skip would have been the 0*Inf / 0*NaN product that
yields NaN. Sxy then remains 0, so covar_pop(), covar_samp(),
and regr_sxy() return 0 instead of NaN.  Inf in the first row is
still fine, because the first-input path already forces Sxy to NaN.

Force Sxy to NaN in that short-circuit path when either new input is
Inf or NaN, matching the first-input handling. Finite constant
inputs still produce exact zero.

Reported-by: Junwen An <feasiblechart@gmail.com>
Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
---
 src/backend/utils/adt/float.c            |  7 +++++++
 src/test/regress/expected/aggregates.out | 22 ++++++++++++++++++++++
 src/test/regress/sql/aggregates.sql      | 12 ++++++++++++
 3 files changed, 41 insertions(+)

diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 4c2ccdfbf3f..aef03cfebb1 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -3449,6 +3449,10 @@ float8_regr_accum(PG_FUNCTION_ARGS)
 		 * etc).  Updating them would just create the possibility of injecting
 		 * roundoff error, and we need exact zero results so that the final
 		 * functions will return NULL in the right cases.
+		 *
+		 * Inf/NaN inputs must still force Sxy to NaN when the other variable
+		 * is constant and the Sxy update is skipped.  Otherwise we leave a
+		 * false exact-zero Sxy.  The first-input path below does the same.
 		 */
 		if (isnan(commonX))
 			Sxx += tmpX * tmpX * scale;
@@ -3456,6 +3460,9 @@ float8_regr_accum(PG_FUNCTION_ARGS)
 			Syy += tmpY * tmpY * scale;
 		if (isnan(commonX) && isnan(commonY))
 			Sxy += tmpX * tmpY * scale;
+		else if (isnan(newvalX) || isinf(newvalX) ||
+				 isnan(newvalY) || isinf(newvalY))
+			Sxy = get_float8_nan();
 
 		/*
 		 * Overflow check.  We only report an overflow error when finite
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 5f0668382ed..82666a729cc 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -515,6 +515,28 @@ SELECT covar_pop(1::float8,'nan'::float8), covar_samp(3::float8,'nan'::float8);
        NaN |           
 (1 row)
 
+-- Inf/NaN not first, other arg constant: must still yield NaN, not 0
+CREATE TEMP TABLE regr_inf_pos (ord int, y float8);
+INSERT INTO regr_inf_pos VALUES (1, 3), (2, 'Infinity'), (3, 4);
+SELECT covar_pop(0::float8, y ORDER BY ord),
+       covar_pop(y, 0::float8 ORDER BY ord),
+       regr_sxy(0::float8, y ORDER BY ord)
+  FROM regr_inf_pos;
+ covar_pop | covar_pop | regr_sxy 
+-----------+-----------+----------
+       NaN |       NaN |      NaN
+(1 row)
+
+DELETE FROM regr_inf_pos;
+INSERT INTO regr_inf_pos VALUES (1, 3), (2, 'NaN'), (3, 4);
+SELECT covar_pop(0::float8, y ORDER BY ord) FROM regr_inf_pos;
+ covar_pop 
+-----------
+       NaN
+(1 row)
+
+DROP TABLE regr_inf_pos;
+
 -- check some cases that formerly had poor roundoff-error behavior
 -- note: regr_r2() differs from corr() for a horizontal line, per spec
 SELECT corr(0.09, g), regr_r2(0.09, g)
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index b788152f0c2..3c615f7dc60 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -140,6 +140,18 @@ SELECT covar_pop(1::float8,2::float8), covar_samp(3::float8,4::float8);
 SELECT covar_pop(1::float8,'inf'::float8), covar_samp(3::float8,'inf'::float8);
 SELECT covar_pop(1::float8,'nan'::float8), covar_samp(3::float8,'nan'::float8);
 
+-- Inf/NaN not first, other arg constant: must still yield NaN, not 0
+CREATE TEMP TABLE regr_inf_pos (ord int, y float8);
+INSERT INTO regr_inf_pos VALUES (1, 3), (2, 'Infinity'), (3, 4);
+SELECT covar_pop(0::float8, y ORDER BY ord),
+       covar_pop(y, 0::float8 ORDER BY ord),
+       regr_sxy(0::float8, y ORDER BY ord)
+  FROM regr_inf_pos;
+DELETE FROM regr_inf_pos;
+INSERT INTO regr_inf_pos VALUES (1, 3), (2, 'NaN'), (3, 4);
+SELECT covar_pop(0::float8, y ORDER BY ord) FROM regr_inf_pos;
+DROP TABLE regr_inf_pos;
+
 -- check some cases that formerly had poor roundoff-error behavior
 -- note: regr_r2() differs from corr() for a horizontal line, per spec
 SELECT corr(0.09, g), regr_r2(0.09, g)
-- 
2.53.0

