From 6be6810993a6d86e93b98513c3dde89081f489c0 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Fri, 6 Nov 2026 15:21:29 +0530 Subject: [PATCH v4] Fix NaN handling in cube Cube's comparison functions use raw floating-point comparisons, so a NaN coordinate can compare equal to a finite value. Min and Max also make the selected bounds depend on the order of the corners. Use the float8 comparison helpers instead, following PostgreSQL's convention that NaNs compare equal and sort after all other values. Use the same comparisons for point detection, so equal NaN corners are treated consistently across constructors. cube_size() now also recognizes points without the point flag, which cube_subset() and older versions can produce. The distance functions can also treat a NaN coordinate as an overlapping projection and return a finite distance. Return NaN for such projections, and use float8_max() to preserve NaN when computing the Chebyshev maximum. An internal GiST key containing NaN can cover finite values, so NaN is not a lower bound on its descendants' distances. Return negative infinity in that case, while leaving leaf distances unchanged. Use the float8 helpers for GiST coordinate bounds as well, matching the SQL operators. Existing B-tree and GiST indexes containing NaN cube coordinates need to be reindexed. Bug: #19603 Reported-by: Yuelin Wang <1217816127@qq.com> Reported-by: Aleksander Alekseev Author: Ayush Tiwari Discussion: https://postgr.es/m/19603-7b1f783d5bbfe791@postgresql.org --- contrib/cube/cube.c | 144 +++++++++++++++++---------------- contrib/cube/cubeparse.y | 5 +- contrib/cube/expected/cube.out | 73 +++++++++++++++++ contrib/cube/sql/cube.sql | 26 ++++++ doc/src/sgml/cube.sgml | 7 ++ 5 files changed, 182 insertions(+), 73 deletions(-) diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c index 28d48549b36..edccc82889a 100644 --- a/contrib/cube/cube.c +++ b/contrib/cube/cube.c @@ -177,7 +177,7 @@ cube_a_f8_f8(PG_FUNCTION_ARGS) point = true; for (i = 0; i < dim; i++) { - if (dur[i] != dll[i]) + if (float8_ne(dur[i], dll[i])) { point = false; break; @@ -779,20 +779,18 @@ cube_union_v0(NDBOX *a, NDBOX *b) /* First compute the union of the dimensions present in both args */ for (i = 0; i < DIM(b); i++) { - result->x[i] = Min(Min(LL_COORD(a, i), UR_COORD(a, i)), - Min(LL_COORD(b, i), UR_COORD(b, i))); - result->x[i + DIM(a)] = Max(Max(LL_COORD(a, i), UR_COORD(a, i)), - Max(LL_COORD(b, i), UR_COORD(b, i))); + result->x[i] = float8_min(float8_min(LL_COORD(a, i), UR_COORD(a, i)), + float8_min(LL_COORD(b, i), UR_COORD(b, i))); + result->x[i + DIM(a)] = float8_max(float8_max(LL_COORD(a, i), UR_COORD(a, i)), + float8_max(LL_COORD(b, i), UR_COORD(b, i))); } /* continue on the higher dimensions only present in 'a' */ for (; i < DIM(a); i++) { - result->x[i] = Min(0, - Min(LL_COORD(a, i), UR_COORD(a, i)) - ); - result->x[i + dim] = Max(0, - Max(LL_COORD(a, i), UR_COORD(a, i)) - ); + result->x[i] = float8_min(0.0, + float8_min(LL_COORD(a, i), UR_COORD(a, i))); + result->x[i + dim] = float8_max(0.0, + float8_max(LL_COORD(a, i), UR_COORD(a, i))); } /* @@ -854,20 +852,18 @@ cube_inter(PG_FUNCTION_ARGS) /* First compute intersection of the dimensions present in both args */ for (i = 0; i < DIM(b); i++) { - result->x[i] = Max(Min(LL_COORD(a, i), UR_COORD(a, i)), - Min(LL_COORD(b, i), UR_COORD(b, i))); - result->x[i + DIM(a)] = Min(Max(LL_COORD(a, i), UR_COORD(a, i)), - Max(LL_COORD(b, i), UR_COORD(b, i))); + result->x[i] = float8_max(float8_min(LL_COORD(a, i), UR_COORD(a, i)), + float8_min(LL_COORD(b, i), UR_COORD(b, i))); + result->x[i + DIM(a)] = float8_min(float8_max(LL_COORD(a, i), UR_COORD(a, i)), + float8_max(LL_COORD(b, i), UR_COORD(b, i))); } /* continue on the higher dimensions only present in 'a' */ for (; i < DIM(a); i++) { - result->x[i] = Max(0, - Min(LL_COORD(a, i), UR_COORD(a, i)) - ); - result->x[i + DIM(a)] = Min(0, - Max(LL_COORD(a, i), UR_COORD(a, i)) - ); + result->x[i] = float8_max(0.0, + float8_min(LL_COORD(a, i), UR_COORD(a, i))); + result->x[i + DIM(a)] = float8_min(0.0, + float8_max(LL_COORD(a, i), UR_COORD(a, i))); } /* @@ -922,7 +918,7 @@ rt_cube_size(NDBOX *a, double *size) /* special case for GiST */ result = 0.0; } - else if (IS_POINT(a) || DIM(a) == 0) + else if (cube_is_point_internal(a)) { /* necessarily has zero size */ result = 0.0; @@ -951,20 +947,20 @@ cube_cmp_v0(NDBOX *a, NDBOX *b) /* compare the common dimensions */ for (i = 0; i < dim; i++) { - if (Min(LL_COORD(a, i), UR_COORD(a, i)) > - Min(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_gt(float8_min(LL_COORD(a, i), UR_COORD(a, i)), + float8_min(LL_COORD(b, i), UR_COORD(b, i)))) return 1; - if (Min(LL_COORD(a, i), UR_COORD(a, i)) < - Min(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_lt(float8_min(LL_COORD(a, i), UR_COORD(a, i)), + float8_min(LL_COORD(b, i), UR_COORD(b, i)))) return -1; } for (i = 0; i < dim; i++) { - if (Max(LL_COORD(a, i), UR_COORD(a, i)) > - Max(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_gt(float8_max(LL_COORD(a, i), UR_COORD(a, i)), + float8_max(LL_COORD(b, i), UR_COORD(b, i)))) return 1; - if (Max(LL_COORD(a, i), UR_COORD(a, i)) < - Max(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_lt(float8_max(LL_COORD(a, i), UR_COORD(a, i)), + float8_max(LL_COORD(b, i), UR_COORD(b, i)))) return -1; } @@ -973,16 +969,16 @@ cube_cmp_v0(NDBOX *a, NDBOX *b) { for (i = dim; i < DIM(a); i++) { - if (Min(LL_COORD(a, i), UR_COORD(a, i)) > 0) + if (float8_gt(float8_min(LL_COORD(a, i), UR_COORD(a, i)), 0.0)) return 1; - if (Min(LL_COORD(a, i), UR_COORD(a, i)) < 0) + if (float8_lt(float8_min(LL_COORD(a, i), UR_COORD(a, i)), 0.0)) return -1; } for (i = dim; i < DIM(a); i++) { - if (Max(LL_COORD(a, i), UR_COORD(a, i)) > 0) + if (float8_gt(float8_max(LL_COORD(a, i), UR_COORD(a, i)), 0.0)) return 1; - if (Max(LL_COORD(a, i), UR_COORD(a, i)) < 0) + if (float8_lt(float8_max(LL_COORD(a, i), UR_COORD(a, i)), 0.0)) return -1; } @@ -996,16 +992,16 @@ cube_cmp_v0(NDBOX *a, NDBOX *b) { for (i = dim; i < DIM(b); i++) { - if (Min(LL_COORD(b, i), UR_COORD(b, i)) > 0) + if (float8_gt(float8_min(LL_COORD(b, i), UR_COORD(b, i)), 0.0)) return -1; - if (Min(LL_COORD(b, i), UR_COORD(b, i)) < 0) + if (float8_lt(float8_min(LL_COORD(b, i), UR_COORD(b, i)), 0.0)) return 1; } for (i = dim; i < DIM(b); i++) { - if (Max(LL_COORD(b, i), UR_COORD(b, i)) > 0) + if (float8_gt(float8_max(LL_COORD(b, i), UR_COORD(b, i)), 0.0)) return -1; - if (Max(LL_COORD(b, i), UR_COORD(b, i)) < 0) + if (float8_lt(float8_max(LL_COORD(b, i), UR_COORD(b, i)), 0.0)) return 1; } @@ -1154,11 +1150,11 @@ cube_contains_v0(NDBOX *a, NDBOX *b) /* Can't care less about the excess dimensions of (a), if any */ for (i = 0; i < Min(DIM(a), DIM(b)); i++) { - if (Min(LL_COORD(a, i), UR_COORD(a, i)) > - Min(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_gt(float8_min(LL_COORD(a, i), UR_COORD(a, i)), + float8_min(LL_COORD(b, i), UR_COORD(b, i)))) return false; - if (Max(LL_COORD(a, i), UR_COORD(a, i)) < - Max(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_lt(float8_max(LL_COORD(a, i), UR_COORD(a, i)), + float8_max(LL_COORD(b, i), UR_COORD(b, i)))) return false; } @@ -1217,18 +1213,20 @@ cube_overlap_v0(NDBOX *a, NDBOX *b) /* compare within the dimensions of (b) */ for (i = 0; i < DIM(b); i++) { - if (Min(LL_COORD(a, i), UR_COORD(a, i)) > Max(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_gt(float8_min(LL_COORD(a, i), UR_COORD(a, i)), + float8_max(LL_COORD(b, i), UR_COORD(b, i)))) return false; - if (Max(LL_COORD(a, i), UR_COORD(a, i)) < Min(LL_COORD(b, i), UR_COORD(b, i))) + if (float8_lt(float8_max(LL_COORD(a, i), UR_COORD(a, i)), + float8_min(LL_COORD(b, i), UR_COORD(b, i)))) return false; } /* compare to zero those dimensions in (a) absent in (b) */ for (i = DIM(b); i < DIM(a); i++) { - if (Min(LL_COORD(a, i), UR_COORD(a, i)) > 0) + if (float8_gt(float8_min(LL_COORD(a, i), UR_COORD(a, i)), 0.0)) return false; - if (Max(LL_COORD(a, i), UR_COORD(a, i)) < 0) + if (float8_lt(float8_max(LL_COORD(a, i), UR_COORD(a, i)), 0.0)) return false; } @@ -1377,16 +1375,14 @@ 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) - distance = d; + distance = float8_max(distance, d); } /* compute distance to zero for those dimensions in (a) absent in (b) */ 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) - distance = d; + distance = float8_max(distance, d); } if (swapped) @@ -1452,9 +1448,9 @@ g_cube_distance(PG_FUNCTION_ARGS) { /* For leaf just return required upper/lower bound */ if (upper) - retval = Max(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_max(cube->x[index], cube->x[index + DIM(cube)]); else - retval = Min(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_min(cube->x[index], cube->x[index + DIM(cube)]); } else { @@ -1466,9 +1462,9 @@ g_cube_distance(PG_FUNCTION_ARGS) * inversed value. */ if (!inverse) - retval = Min(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_min(cube->x[index], cube->x[index + DIM(cube)]); else - retval = Max(cube->x[index], cube->x[index + DIM(cube)]); + retval = float8_max(cube->x[index], cube->x[index + DIM(cube)]); } } } @@ -1505,12 +1501,26 @@ g_cube_distance(PG_FUNCTION_ARGS) break; } } + + /* NaN does not bound the subtree's distances from below */ + if (!GIST_LEAF(entry) && isnan(retval)) + retval = -get_float8_infinity(); + PG_RETURN_FLOAT8(retval); } 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)); @@ -1543,16 +1553,10 @@ cube_is_point_internal(NDBOX *cube) if (IS_POINT(cube)) return true; - /* - * Even if the point-flag is not set, all the lower-left coordinates might - * match the upper-right coordinates, so that the value is in fact a - * point. Such values don't arise with current code - the point flag is - * always set if appropriate - but they might be present on-disk in - * clusters upgraded from pre-9.4 versions. - */ + /* The point flag isn't always set when the corners are equal */ for (i = 0; i < DIM(cube); i++) { - if (LL_COORD(cube, i) != UR_COORD(cube, i)) + if (float8_ne(LL_COORD(cube, i), UR_COORD(cube, i))) return false; } return true; @@ -1578,7 +1582,7 @@ cube_ll_coord(PG_FUNCTION_ARGS) double result; if (DIM(c) >= n && n > 0) - result = Min(LL_COORD(c, n - 1), UR_COORD(c, n - 1)); + result = float8_min(LL_COORD(c, n - 1), UR_COORD(c, n - 1)); else result = 0; @@ -1595,7 +1599,7 @@ cube_ur_coord(PG_FUNCTION_ARGS) double result; if (DIM(c) >= n && n > 0) - result = Max(LL_COORD(c, n - 1), UR_COORD(c, n - 1)); + result = float8_max(LL_COORD(c, n - 1), UR_COORD(c, n - 1)); else result = 0; @@ -1685,9 +1689,9 @@ cube_coord_llur(PG_FUNCTION_ARGS) else { if (upper) - result = Max(cube->x[index], cube->x[index + DIM(cube)]); + result = float8_max(cube->x[index], cube->x[index + DIM(cube)]); else - result = Min(cube->x[index], cube->x[index + DIM(cube)]); + result = float8_min(cube->x[index], cube->x[index + DIM(cube)]); } } else @@ -1734,7 +1738,7 @@ cube_enlarge(PG_FUNCTION_ARGS) for (i = 0, j = dim; i < DIM(a); i++, j++) { - if (LL_COORD(a, i) >= UR_COORD(a, i)) + if (float8_ge(LL_COORD(a, i), UR_COORD(a, i))) { result->x[i] = UR_COORD(a, i) - r; result->x[j] = LL_COORD(a, i) + r; @@ -1744,7 +1748,7 @@ cube_enlarge(PG_FUNCTION_ARGS) result->x[i] = LL_COORD(a, i) - r; result->x[j] = UR_COORD(a, i) + r; } - if (result->x[i] > result->x[j]) + if (float8_gt(result->x[i], result->x[j])) { result->x[i] = (result->x[i] + result->x[j]) / 2; result->x[j] = result->x[i]; @@ -1799,7 +1803,7 @@ cube_f8_f8(PG_FUNCTION_ARGS) NDBOX *result; int size; - if (x0 == x1) + if (float8_eq(x0, x1)) { size = POINT_SIZE(1); result = (NDBOX *) palloc0(size); @@ -1889,7 +1893,7 @@ cube_c_f8_f8(PG_FUNCTION_ARGS) errdetail("A cube cannot have more than %d dimensions.", CUBE_MAX_DIM))); - if (IS_POINT(cube) && (x1 == x2)) + if (IS_POINT(cube) && float8_eq(x1, x2)) { size = POINT_SIZE((DIM(cube) + 1)); result = (NDBOX *) palloc0(size); diff --git a/contrib/cube/cubeparse.y b/contrib/cube/cubeparse.y index c6e657ca939..e9ce6798dee 100644 --- a/contrib/cube/cubeparse.y +++ b/contrib/cube/cubeparse.y @@ -223,8 +223,7 @@ write_box(int dim, char *str1, char *str2, bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext); if (SOFT_ERROR_OCCURRED(escontext)) return false; - /* code this way to do right thing with NaN */ - point &= (bp->x[i] == bp->x[0]); + point &= float8_eq(bp->x[i], bp->x[0]); i++; } while ((s = strchr(s, ',')) != NULL) @@ -233,7 +232,7 @@ write_box(int dim, char *str1, char *str2, bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext); if (SOFT_ERROR_OCCURRED(escontext)) return false; - point &= (bp->x[i] == bp->x[i - dim]); + point &= float8_eq(bp->x[i], bp->x[i - dim]); i++; } Assert(i == dim * 2); diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out index 47787c50bd9..5d7a16b27c0 100644 --- a/contrib/cube/expected/cube.out +++ b/contrib/cube/expected/cube.out @@ -463,12 +463,24 @@ SELECT cube('(1,2),(1,2)'); -- cube_in (1, 2) (1 row) +SELECT cube('(NaN,NaN),(NaN,NaN)'); -- cube_in + cube +------------ + (NaN, NaN) +(1 row) + SELECT cube('{0,1,2}'::float[], '{0,1,2}'::float[]); -- cube_a_f8_f8 cube ----------- (0, 1, 2) (1 row) +SELECT cube('{0,1,NaN}'::float[], '{0,1,NaN}'::float[]); -- cube_a_f8_f8 + cube +------------- + (0, 1, NaN) +(1 row) + SELECT cube('{5,6,7,8}'::float[]); -- cube_a_f8 cube -------------- @@ -487,6 +499,12 @@ SELECT cube(1.37, 1.37); -- cube_f8_f8 (1.37) (1 row) +SELECT cube('NaN'::float8, 'NaN'::float8); -- cube_f8_f8 + cube +------- + (NaN) +(1 row) + SELECT cube(cube(1,1), 42); -- cube_c_f8 cube --------- @@ -505,6 +523,12 @@ SELECT cube(cube(1,1), 42, 42); -- cube_c_f8_f8 (1, 42) (1 row) +SELECT cube(cube(1,1), 'NaN'::float8, 'NaN'::float8); -- cube_c_f8_f8 + cube +---------- + (1, NaN) +(1 row) + SELECT cube(cube(1,1), 42, 24); -- cube_c_f8_f8 cube ----------------- @@ -705,6 +729,13 @@ SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; f (1 row) +SELECT '(1,1)'::cube = '(NaN,NaN)'::cube AS eq, + '(NaN)'::cube > '(1)'::cube AS gt; + eq | gt +----+---- + f | t +(1 row) + -- "overlap" -- SELECT '1'::cube && '1'::cube AS bool; @@ -1151,6 +1182,12 @@ SELECT cube_is_point('(0,1,2),(0,1,2)'::cube); t (1 row) +SELECT cube_is_point(cube(ARRAY['NaN'::float8], ARRAY['NaN'::float8])); + cube_is_point +--------------- + t +(1 row) + SELECT cube_is_point('(0,1,2),(-1,1,2)'::cube); cube_is_point --------------- @@ -1381,6 +1418,12 @@ SELECT cube_size('(42,137)'::cube); 0 (1 row) +SELECT cube_size(cube_subset('(NaN,0),(NaN,1)'::cube, ARRAY[1])); + cube_size +----------- + 0 +(1 row) + -- Test of distances (euclidean distance may not be bit-exact) -- SET extra_float_digits = 0; @@ -1440,6 +1483,14 @@ SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); 0 (1 row) +SELECT '(NaN,1)'::cube <-> '(2,2)'::cube AS euclidean, + '(NaN,1)'::cube <#> '(2,2)'::cube AS taxicab, + '(NaN,1)'::cube <=> '(2,2)'::cube AS chebyshev; + euclidean | taxicab | chebyshev +-----------+---------+----------- + NaN | NaN | NaN +(1 row) + -- coordinate access SELECT cube(array[10,20,30], array[40,50,60])->1; ?column? @@ -1973,3 +2024,25 @@ SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upp (15 rows) RESET enable_indexscan; +-- Opposite coordinate orders cannot both follow the index's page order. +TRUNCATE test_cube; +INSERT INTO test_cube +SELECT cube(ARRAY[number::float8, -number::float8], + ARRAY['NaN'::float8, 'NaN'::float8]) +FROM generate_series(1, 1000) AS numbers(number); +SET enable_seqscan = false; +SELECT ARRAY(SELECT c~>(-1) FROM test_cube ORDER BY c~>(-1)) = + ARRAY(SELECT generate_series(-1000, -1)::float8) AS correct_order; + correct_order +--------------- + t +(1 row) + +SELECT ARRAY(SELECT c~>(-3) FROM test_cube ORDER BY c~>(-3)) = + ARRAY(SELECT generate_series(1, 1000)::float8) AS correct_order; + correct_order +--------------- + t +(1 row) + +RESET enable_seqscan; diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql index eec90d21ee3..1a249533dd2 100644 --- a/contrib/cube/sql/cube.sql +++ b/contrib/cube/sql/cube.sql @@ -125,13 +125,17 @@ SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_se -- Test point processing -- SELECT cube('(1,2),(1,2)'); -- cube_in +SELECT cube('(NaN,NaN),(NaN,NaN)'); -- cube_in SELECT cube('{0,1,2}'::float[], '{0,1,2}'::float[]); -- cube_a_f8_f8 +SELECT cube('{0,1,NaN}'::float[], '{0,1,NaN}'::float[]); -- cube_a_f8_f8 SELECT cube('{5,6,7,8}'::float[]); -- cube_a_f8 SELECT cube(1.37); -- cube_f8 SELECT cube(1.37, 1.37); -- cube_f8_f8 +SELECT cube('NaN'::float8, 'NaN'::float8); -- cube_f8_f8 SELECT cube(cube(1,1), 42); -- cube_c_f8 SELECT cube(cube(1,2), 42); -- cube_c_f8 SELECT cube(cube(1,1), 42, 42); -- cube_c_f8_f8 +SELECT cube(cube(1,1), 'NaN'::float8, 'NaN'::float8); -- cube_c_f8_f8 SELECT cube(cube(1,1), 42, 24); -- cube_c_f8_f8 SELECT cube(cube(1,2), 42, 42); -- cube_c_f8_f8 SELECT cube(cube(1,2), 42, 24); -- cube_c_f8_f8 @@ -189,6 +193,8 @@ SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; +SELECT '(1,1)'::cube = '(NaN,NaN)'::cube AS eq, + '(NaN)'::cube > '(1)'::cube AS gt; -- "overlap" -- @@ -292,6 +298,7 @@ SELECT cube_ur_coord('(42,137)'::cube, 3); SELECT cube_is_point('(0)'::cube); SELECT cube_is_point('(0,1,2)'::cube); SELECT cube_is_point('(0,1,2),(0,1,2)'::cube); +SELECT cube_is_point(cube(ARRAY['NaN'::float8], ARRAY['NaN'::float8])); SELECT cube_is_point('(0,1,2),(-1,1,2)'::cube); SELECT cube_is_point('(0,1,2),(0,-1,2)'::cube); SELECT cube_is_point('(0,1,2),(0,1,-2)'::cube); @@ -341,6 +348,7 @@ SELECT cube_inter('(1,2,3)'::cube, '(5,6,3)'::cube); -- point args -- SELECT cube_size('(4,8),(15,16)'::cube); SELECT cube_size('(42,137)'::cube); +SELECT cube_size(cube_subset('(NaN,0),(NaN,1)'::cube, ARRAY[1])); -- Test of distances (euclidean distance may not be bit-exact) -- @@ -356,6 +364,11 @@ 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); + +SELECT '(NaN,1)'::cube <-> '(2,2)'::cube AS euclidean, + '(NaN,1)'::cube <#> '(2,2)'::cube AS taxicab, + '(NaN,1)'::cube <=> '(2,2)'::cube AS chebyshev; + -- coordinate access SELECT cube(array[10,20,30], array[40,50,60])->1; SELECT cube(array[40,50,60], array[10,20,30])->1; @@ -436,3 +449,16 @@ SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by rig SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound RESET enable_indexscan; + +-- Opposite coordinate orders cannot both follow the index's page order. +TRUNCATE test_cube; +INSERT INTO test_cube +SELECT cube(ARRAY[number::float8, -number::float8], + ARRAY['NaN'::float8, 'NaN'::float8]) +FROM generate_series(1, 1000) AS numbers(number); +SET enable_seqscan = false; +SELECT ARRAY(SELECT c~>(-1) FROM test_cube ORDER BY c~>(-1)) = + ARRAY(SELECT generate_series(-1000, -1)::float8) AS correct_order; +SELECT ARRAY(SELECT c~>(-3) FROM test_cube ORDER BY c~>(-3)) = + ARRAY(SELECT generate_series(1, 1000)::float8) AS correct_order; +RESET enable_seqscan; diff --git a/doc/src/sgml/cube.sgml b/doc/src/sgml/cube.sgml index a11c0cbd767..a9ada6a64cb 100644 --- a/doc/src/sgml/cube.sgml +++ b/doc/src/sgml/cube.sgml @@ -106,6 +106,13 @@ Values are stored internally as 64-bit floating point numbers. This means that numbers with more than about 16 significant digits will be truncated. + + + As with double precision, NaN coordinates + compare equal to one another and greater than all non-NaN + values. Distance calculations return NaN if either cube + has a NaN coordinate. + -- 2.34.1