PG_FUNCTION_INFO_V1(gbt_float4_penalty);
PG_FUNCTION_INFO_V1(gbt_float4_same);
+/*
+ * Use the NaN-aware comparators from utils/float.h, so that our results
+ * will agree with standard btree indexes. Note that penalty and distance
+ * functions below must also cope with NaNs, in particular with the policy
+ * that all NaNs are equal.
+ */
static bool
gbt_float4gt(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float4 *) a) > *((const float4 *) b));
+ return float4_gt(*((const float4 *) a), *((const float4 *) b));
}
static bool
gbt_float4ge(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float4 *) a) >= *((const float4 *) b));
+ return float4_ge(*((const float4 *) a), *((const float4 *) b));
}
static bool
gbt_float4eq(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float4 *) a) == *((const float4 *) b));
+ return float4_eq(*((const float4 *) a), *((const float4 *) b));
}
static bool
gbt_float4le(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float4 *) a) <= *((const float4 *) b));
+ return float4_le(*((const float4 *) a), *((const float4 *) b));
}
static bool
gbt_float4lt(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float4 *) a) < *((const float4 *) b));
+ return float4_lt(*((const float4 *) a), *((const float4 *) b));
}
static int
{
float4KEY *ia = (float4KEY *) (((const Nsrt *) a)->t);
float4KEY *ib = (float4KEY *) (((const Nsrt *) b)->t);
+ int res;
- if (ia->lower == ib->lower)
- {
- if (ia->upper == ib->upper)
- return 0;
-
- return (ia->upper > ib->upper) ? 1 : -1;
- }
-
- return (ia->lower > ib->lower) ? 1 : -1;
+ res = float4_cmp_internal(ia->lower, ib->lower);
+ if (res != 0)
+ return res;
+ return float4_cmp_internal(ia->upper, ib->upper);
}
static float8
gbt_float4_dist(const void *a, const void *b, FmgrInfo *flinfo)
{
- return GET_FLOAT_DISTANCE(float4, a, b);
+ float8 arg1 = *(const float4 *) a;
+ float8 arg2 = *(const float4 *) b;
+ float8 r;
+
+ r = arg1 - arg2;
+ /* needn't consider isinf case here, must be due to input infinity */
+ if (unlikely(isnan(r)))
+ {
+ if (isnan(arg1) && isnan(arg2))
+ r = 0.0; /* treat NaNs as equal */
+ else if (isnan(arg1) || isnan(arg2))
+ r = get_float8_infinity(); /* max dist for NaN vs non-NaN */
+ else
+ r = 0.0; /* must be Inf - Inf case */
+ }
+ return fabs(r);
}
r = a - b;
CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
+ if (unlikely(isnan(r)))
+ {
+ if (isnan(a) && isnan(b))
+ r = 0.0; /* treat NaNs as equal */
+ else if (isnan(a) || isnan(b))
+ r = get_float4_infinity(); /* max dist for NaN vs non-NaN */
+ else
+ r = 0.0; /* must be Inf - Inf case */
+ }
PG_RETURN_FLOAT4(Abs(r));
}
float4KEY *newentry = (float4KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
float *result = (float *) PG_GETARG_POINTER(2);
- penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
+ float_penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
PG_RETURN_POINTER(result);
PG_FUNCTION_INFO_V1(gbt_float8_same);
+/*
+ * Use the NaN-aware comparators from utils/float.h, so that our results
+ * will agree with standard btree indexes. Note that penalty and distance
+ * functions below must also cope with NaNs, in particular with the policy
+ * that all NaNs are equal.
+ */
static bool
gbt_float8gt(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float8 *) a) > *((const float8 *) b));
+ return float8_gt(*((const float8 *) a), *((const float8 *) b));
}
static bool
gbt_float8ge(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float8 *) a) >= *((const float8 *) b));
+ return float8_ge(*((const float8 *) a), *((const float8 *) b));
}
static bool
gbt_float8eq(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float8 *) a) == *((const float8 *) b));
+ return float8_eq(*((const float8 *) a), *((const float8 *) b));
}
static bool
gbt_float8le(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float8 *) a) <= *((const float8 *) b));
+ return float8_le(*((const float8 *) a), *((const float8 *) b));
}
static bool
gbt_float8lt(const void *a, const void *b, FmgrInfo *flinfo)
{
- return (*((const float8 *) a) < *((const float8 *) b));
+ return float8_lt(*((const float8 *) a), *((const float8 *) b));
}
static int
{
float8KEY *ia = (float8KEY *) (((const Nsrt *) a)->t);
float8KEY *ib = (float8KEY *) (((const Nsrt *) b)->t);
+ int res;
- if (ia->lower == ib->lower)
- {
- if (ia->upper == ib->upper)
- return 0;
-
- return (ia->upper > ib->upper) ? 1 : -1;
- }
-
- return (ia->lower > ib->lower) ? 1 : -1;
+ res = float8_cmp_internal(ia->lower, ib->lower);
+ if (res != 0)
+ return res;
+ return float8_cmp_internal(ia->upper, ib->upper);
}
static float8
r = arg1 - arg2;
CHECKFLOATVAL(r, isinf(arg1) || isinf(arg2), true);
-
+ if (unlikely(isnan(r)))
+ {
+ if (isnan(arg1) && isnan(arg2))
+ r = 0.0; /* treat NaNs as equal */
+ else if (isnan(arg1) || isnan(arg2))
+ r = get_float8_infinity(); /* max dist for NaN vs non-NaN */
+ else
+ r = 0.0; /* must be Inf - Inf case */
+ }
return Abs(r);
}
r = a - b;
CHECKFLOATVAL(r, isinf(a) || isinf(b), true);
-
+ if (unlikely(isnan(r)))
+ {
+ if (isnan(a) && isnan(b))
+ r = 0.0; /* treat NaNs as equal */
+ else if (isnan(a) || isnan(b))
+ r = get_float8_infinity(); /* max dist for NaN vs non-NaN */
+ else
+ r = 0.0; /* must be Inf - Inf case */
+ }
PG_RETURN_FLOAT8(Abs(r));
}
float8KEY *newentry = (float8KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
float *result = (float *) PG_GETARG_POINTER(2);
- penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
+ float_penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
PG_RETURN_POINTER(result);
#include "access/gist.h"
#include "btree_gist.h"
+#include "utils/float.h"
#include "utils/rel.h"
typedef char GBT_NUMKEY;
/*
- * Note: The factor 0.49 in following macro avoids floating point overflows
+ * Compute penalty for expanding a range olower..oupper to nlower..nupper.
+ *
+ * Although the arguments are declared double, they must not be NaN nor
+ * large enough to risk overflows in the calculations herein. We only
+ * actually use this for integral data types, so there's no hazard.
+ */
+static inline float
+penalty_num_impl(double olower, double oupper,
+ double nlower, double nupper,
+ int natts)
+{
+ float result = 0.0F;
+ double tmp = 0.0;
+
+ /* Add penalty for expanding upper bound */
+ if (nupper > oupper)
+ tmp += nupper - oupper;
+ /* Add penalty for expanding lower bound */
+ if (olower > nlower)
+ tmp += olower - nlower;
+ if (tmp > 0.0)
+ {
+ /* Ensure result is non-zero, even if next step underflows to zero */
+ result += FLT_MIN;
+ /* Scale penalty to 0 .. 1 */
+ result += (float) (tmp / (tmp + (oupper - olower)));
+ /* Scale to 0 .. FLT_MAX / (natts + 1) */
+ result *= FLT_MAX / (natts + 1);
+ }
+ return result;
+}
+
+/*
+ * As above, but the input values are float4 or float8, so we must cope
+ * with NaNs, infinities, and overflows.
+ */
+static inline float
+float_penalty_num_impl(double olower, double oupper,
+ double nlower, double nupper,
+ int natts)
+{
+ float result = 0.0F;
+ double tmp = 0.0;
+
+ /* Add penalty for expanding upper bound */
+ if (float8_gt(nupper, oupper))
+ {
+ double delta = nupper - oupper;
+
+ if (unlikely(isnan(delta)))
+ {
+ /* oupper couldn't be NaN here, see float8_gt */
+ if (isnan(nupper))
+ delta = FLT_MAX; /* max penalty for NaN vs non-NaN */
+ else
+ delta = 0.0; /* must be Inf - Inf case */
+ }
+ else if (delta > FLT_MAX)
+ delta = FLT_MAX; /* clamp to FLT_MAX, esp for infinity */
+ tmp += delta;
+ }
+ /* Add penalty for expanding lower bound */
+ if (float8_gt(olower, nlower))
+ {
+ double delta = olower - nlower;
+
+ if (unlikely(isnan(delta)))
+ {
+ /* nlower couldn't be NaN here, see float8_gt */
+ if (isnan(olower))
+ delta = FLT_MAX; /* max penalty for NaN vs non-NaN */
+ else
+ delta = 0.0; /* must be Inf - Inf case */
+ }
+ else if (delta > FLT_MAX)
+ delta = FLT_MAX; /* clamp to FLT_MAX, esp for infinity */
+ tmp += delta;
+ }
+ if (tmp > 0.0)
+ {
+ double delta = oupper - olower;
+
+ /* Clamp delta (the original range size) to 0 .. FLT_MAX */
+ if (unlikely(isnan(delta)))
+ {
+ /* here, we must deal with olower possibly being NaN */
+ if (isnan(oupper) && isnan(olower))
+ delta = 0.0; /* treat NaNs as equal */
+ else if (isnan(oupper) || isnan(olower))
+ delta = FLT_MAX; /* max penalty for NaN vs non-NaN */
+ else
+ delta = 0.0; /* must be Inf - Inf case */
+ }
+ else if (delta > FLT_MAX)
+ delta = FLT_MAX; /* clamp to FLT_MAX, esp for infinity */
+ /* Ensure result is non-zero, even if next step underflows to zero */
+ result += FLT_MIN;
+ /* Scale penalty to 0 .. 1 */
+ result += (float) (tmp / (tmp + delta));
+ /* Scale to 0 .. FLT_MAX / (natts + 1) */
+ result *= FLT_MAX / (natts + 1);
+ }
+ return result;
+}
+
+/*
+ * These macros provide backwards-compatible notation for callers.
*/
#define penalty_num(result,olower,oupper,nlower,nupper) do { \
- double tmp = 0.0F; \
- (*(result)) = 0.0F; \
- if ( (nupper) > (oupper) ) \
- tmp += ( ((double)nupper)*0.49F - ((double)oupper)*0.49F ); \
- if ( (olower) > (nlower) ) \
- tmp += ( ((double)olower)*0.49F - ((double)nlower)*0.49F ); \
- if (tmp > 0.0F) \
- { \
- (*(result)) += FLT_MIN; \
- (*(result)) += (float) ( ((double)(tmp)) / ( (double)(tmp) + ( ((double)(oupper))*0.49F - ((double)(olower))*0.49F ) ) ); \
- (*(result)) *= (FLT_MAX / (((GISTENTRY *) PG_GETARG_POINTER(0))->rel->rd_att->natts + 1)); \
- } \
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); \
+ *(result) = penalty_num_impl(olower, oupper, nlower, nupper, \
+ entry->rel->rd_att->natts); \
+} while (0)
+
+#define float_penalty_num(result,olower,oupper,nlower,nupper) do { \
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); \
+ *(result) = float_penalty_num_impl(olower, oupper, nlower, nupper, \
+ entry->rel->rd_att->natts); \
} while (0)
(ivp)->day * (24.0 * SECS_PER_HOUR) + \
(ivp)->month * (30.0 * SECS_PER_DAY))
+/* This macro is not safe to use with actual float inputs, only integers */
#define GET_FLOAT_DISTANCE(t, arg1, arg2) Abs( ((float8) *((const t *) (arg1))) - ((float8) *((const t *) (arg2))) )
/*
\N
2972.381398
220.199877
+Infinity
+-Infinity
+NaN
3542.561032
-2168.024176
-3305.714558
27770.539968
13275.355549
-4267.695804
+Infinity
+-Infinity
+NaN
\N
\N
38915.525185
SELECT count(*) FROM float4tmp WHERE a < -179.0;
count
-------
- 244
+ 245
(1 row)
SELECT count(*) FROM float4tmp WHERE a <= -179.0;
count
-------
- 245
+ 246
(1 row)
SELECT count(*) FROM float4tmp WHERE a = -179.0;
SELECT count(*) FROM float4tmp WHERE a >= -179.0;
count
-------
- 303
+ 305
(1 row)
SELECT count(*) FROM float4tmp WHERE a > -179.0;
count
-------
- 302
+ 304
(1 row)
SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3;
SELECT count(*) FROM float4tmp WHERE a < -179.0::float4;
count
-------
- 244
+ 245
(1 row)
SELECT count(*) FROM float4tmp WHERE a <= -179.0::float4;
count
-------
- 245
+ 246
(1 row)
SELECT count(*) FROM float4tmp WHERE a = -179.0::float4;
SELECT count(*) FROM float4tmp WHERE a >= -179.0::float4;
count
-------
- 303
+ 305
(1 row)
SELECT count(*) FROM float4tmp WHERE a > -179.0::float4;
count
-------
- 302
+ 304
(1 row)
EXPLAIN (COSTS OFF)
-158.17741 | 20.822586
(3 rows)
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float4excl (a float4, EXCLUDE USING gist (a WITH =));
+INSERT INTO float4excl VALUES ('NaN'::float4);
+INSERT INTO float4excl VALUES ('NaN'::float4); -- expect: violates EXCLUDE
+ERROR: conflicting key value violates exclusion constraint "float4excl_a_excl"
+DETAIL: Key (a)=(NaN) conflicts with existing key (a)=(NaN).
+SELECT count(*) FROM float4excl;
+ count
+-------
+ 1
+(1 row)
+
+-- Test double-column index
+CREATE INDEX float4idx2 ON float4tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+ QUERY PLAN
+--------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on float4tmp
+ Recheck Cond: (abs(a) = '179'::real)
+ -> Bitmap Index Scan on float4idx2
+ Index Cond: (abs(a) = '179'::real)
+(5 rows)
+
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+ count
+-------
+ 1
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
SELECT count(*) FROM float8tmp WHERE a < -1890.0;
count
-------
- 237
+ 238
(1 row)
SELECT count(*) FROM float8tmp WHERE a <= -1890.0;
count
-------
- 238
+ 239
(1 row)
SELECT count(*) FROM float8tmp WHERE a = -1890.0;
SELECT count(*) FROM float8tmp WHERE a >= -1890.0;
count
-------
- 307
+ 309
(1 row)
SELECT count(*) FROM float8tmp WHERE a > -1890.0;
count
-------
- 306
+ 308
(1 row)
SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3;
SELECT count(*) FROM float8tmp WHERE a < -1890.0::float8;
count
-------
- 237
+ 238
(1 row)
SELECT count(*) FROM float8tmp WHERE a <= -1890.0::float8;
count
-------
- 238
+ 239
(1 row)
SELECT count(*) FROM float8tmp WHERE a = -1890.0::float8;
SELECT count(*) FROM float8tmp WHERE a >= -1890.0::float8;
count
-------
- 307
+ 309
(1 row)
SELECT count(*) FROM float8tmp WHERE a > -1890.0::float8;
count
-------
- 306
+ 308
(1 row)
EXPLAIN (COSTS OFF)
-1769.73634 | 120.26366000000007
(3 rows)
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float8excl (a float8, EXCLUDE USING gist (a WITH =));
+INSERT INTO float8excl VALUES ('NaN'::float8);
+INSERT INTO float8excl VALUES ('NaN'::float8); -- expect: violates EXCLUDE
+ERROR: conflicting key value violates exclusion constraint "float8excl_a_excl"
+DETAIL: Key (a)=(NaN) conflicts with existing key (a)=(NaN).
+SELECT count(*) FROM float8excl;
+ count
+-------
+ 1
+(1 row)
+
+-- Test double-column index
+CREATE INDEX float8idx2 ON float8tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+ QUERY PLAN
+---------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on float8tmp
+ Recheck Cond: (abs(a) = '1890'::double precision)
+ -> Bitmap Index Scan on float8idx2
+ Index Cond: (abs(a) = '1890'::double precision)
+(5 rows)
+
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+ count
+-------
+ 1
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
SELECT count(*) FROM numerictmp WHERE a < -1890.0;
count
-------
- 505
+ 506
(1 row)
SELECT count(*) FROM numerictmp WHERE a <= -1890.0;
count
-------
- 506
+ 507
(1 row)
SELECT count(*) FROM numerictmp WHERE a = -1890.0;
SELECT count(*) FROM numerictmp WHERE a >= -1890.0;
count
-------
- 597
+ 599
(1 row)
SELECT count(*) FROM numerictmp WHERE a > -1890.0;
count
-------
- 596
+ 598
(1 row)
SELECT count(*) FROM numerictmp WHERE a < 'NaN' ;
count
-------
- 1100
+ 1102
(1 row)
SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ;
count
-------
- 1102
+ 1105
(1 row)
SELECT count(*) FROM numerictmp WHERE a = 'NaN' ;
count
-------
- 2
+ 3
(1 row)
SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ;
count
-------
- 2
+ 3
(1 row)
SELECT count(*) FROM numerictmp WHERE a > 'NaN' ;
SELECT count(*) FROM numerictmp WHERE a < 0 ;
count
-------
- 523
+ 524
(1 row)
SELECT count(*) FROM numerictmp WHERE a <= 0 ;
count
-------
- 526
+ 527
(1 row)
SELECT count(*) FROM numerictmp WHERE a = 0 ;
SELECT count(*) FROM numerictmp WHERE a >= 0 ;
count
-------
- 579
+ 581
(1 row)
SELECT count(*) FROM numerictmp WHERE a > 0 ;
count
-------
- 576
+ 578
(1 row)
CREATE INDEX numericidx ON numerictmp USING gist ( a );
SELECT count(*) FROM numerictmp WHERE a < -1890.0;
count
-------
- 505
+ 506
(1 row)
SELECT count(*) FROM numerictmp WHERE a <= -1890.0;
count
-------
- 506
+ 507
(1 row)
SELECT count(*) FROM numerictmp WHERE a = -1890.0;
SELECT count(*) FROM numerictmp WHERE a >= -1890.0;
count
-------
- 597
+ 599
(1 row)
SELECT count(*) FROM numerictmp WHERE a > -1890.0;
count
-------
- 596
+ 598
(1 row)
SELECT count(*) FROM numerictmp WHERE a < 'NaN' ;
count
-------
- 1100
+ 1102
(1 row)
SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ;
count
-------
- 1102
+ 1105
(1 row)
SELECT count(*) FROM numerictmp WHERE a = 'NaN' ;
count
-------
- 2
+ 3
(1 row)
SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ;
count
-------
- 2
+ 3
(1 row)
SELECT count(*) FROM numerictmp WHERE a > 'NaN' ;
SELECT count(*) FROM numerictmp WHERE a < 0 ;
count
-------
- 523
+ 524
(1 row)
SELECT count(*) FROM numerictmp WHERE a <= 0 ;
count
-------
- 526
+ 527
(1 row)
SELECT count(*) FROM numerictmp WHERE a = 0 ;
SELECT count(*) FROM numerictmp WHERE a >= 0 ;
count
-------
- 579
+ 581
(1 row)
SELECT count(*) FROM numerictmp WHERE a > 0 ;
count
-------
- 576
+ 578
(1 row)
-- Test index-only scans
EXPLAIN (COSTS OFF)
SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3;
SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3;
+
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float4excl (a float4, EXCLUDE USING gist (a WITH =));
+INSERT INTO float4excl VALUES ('NaN'::float4);
+INSERT INTO float4excl VALUES ('NaN'::float4); -- expect: violates EXCLUDE
+SELECT count(*) FROM float4excl;
+
+-- Test double-column index
+CREATE INDEX float4idx2 ON float4tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+SELECT count(*) FROM float4tmp WHERE abs(a) = 179.0::float4;
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;
EXPLAIN (COSTS OFF)
SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3;
SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3;
+
+-- EXCLUDE constraint must block a duplicate NaN, same as it does for finite
+-- values.
+CREATE TABLE float8excl (a float8, EXCLUDE USING gist (a WITH =));
+INSERT INTO float8excl VALUES ('NaN'::float8);
+INSERT INTO float8excl VALUES ('NaN'::float8); -- expect: violates EXCLUDE
+SELECT count(*) FROM float8excl;
+
+-- Test double-column index
+CREATE INDEX float8idx2 ON float8tmp USING gist ( a, abs(a) );
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+SELECT count(*) FROM float8tmp WHERE abs(a) = 1890.0::float8;
+
+RESET enable_seqscan;
+RESET enable_indexscan;
+RESET enable_bitmapscan;