From 8c1372f61b11a1b904f220d87aa4e8b82aa85f06 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Mon, 3 Aug 2026 18:41:19 +0530
Subject: [PATCH] cube: propagate NaN in taxicab, Chebyshev and Euclidean
 distances

The static helper distance_1D() classifies the 1-D projections of two
cubes as left-of, right-of, or intersecting using plain floating-point
comparisons.  When a coordinate is NaN every comparison is false, so the
projection was treated as intersecting and the helper returned 0.
distance_taxicab(), distance_chebyshev() and cube_distance() are all
built on distance_1D(), so a single NaN coordinate silently produced a
finite, plausible-looking distance instead of propagating NaN the way
IEEE 754 arithmetic normally would.

Return NaN from distance_1D() when any endpoint is NaN.  distance_taxicab()
and cube_distance() then propagate it through their running sums, but
distance_chebyshev() keeps a running maximum with "d > distance", and
NaN > x is false, so test for NaN explicitly there as well.

Add regression tests covering the NaN cases.

Reported-by: Yuelin Wang <1217816127@qq.com>
Discussion: https://postgr.es/m/19603-7b1f783d5bbfe791@postgresql.org
---
 contrib/cube/cube.c            | 12 ++++++++--
 contrib/cube/expected/cube.out | 44 ++++++++++++++++++++++++++++++++++
 contrib/cube/sql/cube.sql      |  9 +++++++
 3 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c
index 28d48549b36..f1a95585781 100644
--- a/contrib/cube/cube.c
+++ b/contrib/cube/cube.c
@@ -1377,7 +1377,7 @@ distance_chebyshev(PG_FUNCTION_ARGS)
 	{
 		d = fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i),
 							 LL_COORD(b, i), UR_COORD(b, i)));
-		if (d > distance)
+		if (isnan(d) || d > distance)
 			distance = d;
 	}
 
@@ -1385,7 +1385,7 @@ distance_chebyshev(PG_FUNCTION_ARGS)
 	for (i = DIM(b); i < DIM(a); i++)
 	{
 		d = fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i), 0.0, 0.0));
-		if (d > distance)
+		if (isnan(d) || d > distance)
 			distance = d;
 	}
 
@@ -1511,6 +1511,14 @@ g_cube_distance(PG_FUNCTION_ARGS)
 static double
 distance_1D(double a1, double a2, double b1, double b2)
 {
+	/*
+	 * If any endpoint is NaN, all the comparisons below are false and we'd
+	 * fall through to the "intersecting" case and return 0.  Return NaN
+	 * instead, so that callers propagate it as ordinary float arithmetic does.
+	 */
+	if (isnan(a1) || isnan(a2) || isnan(b1) || isnan(b2))
+		return get_float8_nan();
+
 	/* interval (a) is entirely on the left of (b) */
 	if ((a1 <= b1) && (a2 <= b1) && (a1 <= b2) && (a2 <= b2))
 		return (Min(b1, b2) - Max(a1, a2));
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index 47787c50bd9..95b85c4f2d5 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -1440,6 +1440,50 @@ SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
                 0
 (1 row)
 
+-- NaN coordinates propagate to a NaN distance, instead of being treated
+-- as a zero-distance (overlapping) projection
+SELECT distance_chebyshev('(nan,nan)'::cube, '(1,1)'::cube);
+ distance_chebyshev 
+--------------------
+                NaN
+(1 row)
+
+SELECT distance_chebyshev('(5,5)'::cube, '(1,nan)'::cube);
+ distance_chebyshev 
+--------------------
+                NaN
+(1 row)
+
+SELECT distance_taxicab('(nan)'::cube, '(1)'::cube);
+ distance_taxicab 
+------------------
+              NaN
+(1 row)
+
+SELECT cube_distance('(nan,nan)'::cube, '(1,1)'::cube);
+ cube_distance 
+---------------
+           NaN
+(1 row)
+
+SELECT '(nan,nan)'::cube <-> '(1,1)'::cube as d_e_nan;
+ d_e_nan 
+---------
+     NaN
+(1 row)
+
+SELECT '(5,5)'::cube <=> '(1,nan)'::cube as d_c_nan;
+ d_c_nan 
+---------
+     NaN
+(1 row)
+
+SELECT '(nan)'::cube <#> '(1)'::cube as d_t_nan;
+ d_t_nan 
+---------
+     NaN
+(1 row)
+
 -- coordinate access
 SELECT cube(array[10,20,30], array[40,50,60])->1;
  ?column? 
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index eec90d21ee3..f71b62b069a 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -356,6 +356,15 @@ SELECT '(1,1)'::cube <#> '(4,5)'::cube as d_t;
 SELECT cube_distance('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
 SELECT distance_chebyshev('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
 SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube);
+-- NaN coordinates propagate to a NaN distance, instead of being treated
+-- as a zero-distance (overlapping) projection
+SELECT distance_chebyshev('(nan,nan)'::cube, '(1,1)'::cube);
+SELECT distance_chebyshev('(5,5)'::cube, '(1,nan)'::cube);
+SELECT distance_taxicab('(nan)'::cube, '(1)'::cube);
+SELECT cube_distance('(nan,nan)'::cube, '(1,1)'::cube);
+SELECT '(nan,nan)'::cube <-> '(1,1)'::cube as d_e_nan;
+SELECT '(5,5)'::cube <=> '(1,nan)'::cube as d_c_nan;
+SELECT '(nan)'::cube <#> '(1)'::cube as d_t_nan;
 -- coordinate access
 SELECT cube(array[10,20,30], array[40,50,60])->1;
 SELECT cube(array[40,50,60], array[10,20,30])->1;
-- 
2.34.1

