From 8c1372f61b11a1b904f220d87aa4e8b82aa85f06 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari 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