Thread: Issue with point_ops and NaN

Issue with point_ops and NaN

From
Julien Rouhaud
Date:
Hi,

While running some sanity checks on the regression tests, I found one test that
returns different results depending on whether an index or a sequential scan is
used.

Minimal reproducer:

=# CREATE TABLE point_tbl AS select '(nan,nan)'::point f1;
=# CREATE INDEX ON point_tbl USING gist(f1);

=# EXPLAIN SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
                                  QUERY PLAN
------------------------------------------------------------------------------
 Seq Scan on point_tbl  (cost=0.00..1.01 rows=1 width=16)
   Filter: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon)
(2 rows)

=# SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
    f1
-----------
 (NaN,NaN)
(1 row)

SET enable_seqscan = 0;


=# EXPLAIN SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
 Index Only Scan using point_tbl_f1_idx on point_tbl  (cost=0.12..8.14 rows=1 width=16)
   Index Cond: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon)
(2 rows)

=# SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
 f1 
----
(0 rows)

The discrepancy comes from the fact that the sequential scan checks the
condition using point_inside() / lseg_crossing(), while the gist index will
check the condition using box_overlap() / box_ov(), which have different
opinions on how to handle NaN.

Getting a consistent behavior shouldn't be hard, but I'm unsure which behavior
is actually correct.



Re: Issue with point_ops and NaN

From
Laurenz Albe
Date:
On Tue, 2021-03-30 at 17:57 +0800, Julien Rouhaud wrote:
> While running some sanity checks on the regression tests, I found one test that
> returns different results depending on whether an index or a sequential scan is
> used.
> 
> Minimal reproducer:
> 
> =# CREATE TABLE point_tbl AS select '(nan,nan)'::point f1;
> =# CREATE INDEX ON point_tbl USING gist(f1);
> 
> =# EXPLAIN SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
>                                   QUERY PLAN
> ------------------------------------------------------------------------------
>  Seq Scan on point_tbl  (cost=0.00..1.01 rows=1 width=16)
>    Filter: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon)
> (2 rows)
> 
> =# SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
>     f1
> -----------
>  (NaN,NaN)
> (1 row)
> 
> SET enable_seqscan = 0;
> 
> 
> =# EXPLAIN SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
>                                        QUERY PLAN                                       
> ----------------------------------------------------------------------------------------
>  Index Only Scan using point_tbl_f1_idx on point_tbl  (cost=0.12..8.14 rows=1 width=16)
>    Index Cond: (f1 <@ '((0,0),(0,100),(100,100),(50,50),(100,0),(0,0))'::polygon)
> (2 rows)
> 
> =# SELECT * FROM point_tbl WHERE f1 <@ polygon '(0,0),(0,100),(100,100),(50,50),(100,0),(0,0)';
>  f1 
> ----
> (0 rows)
> 
> The discrepancy comes from the fact that the sequential scan checks the
> condition using point_inside() / lseg_crossing(), while the gist index will
> check the condition using box_overlap() / box_ov(), which have different
> opinions on how to handle NaN.
> 
> Getting a consistent behavior shouldn't be hard, but I'm unsure which behavior
> is actually correct.

I'd say that this is certainly wrong:

SELECT point('NaN','NaN') <@ polygon('(0,0),(1,0),(1,1),(0,0)');

 ?column? 
----------
 t
(1 row)

Yours,
Laurenz Albe




Re: Issue with point_ops and NaN

From
Julien Rouhaud
Date:
On Tue, Mar 30, 2021 at 02:47:05PM +0200, Laurenz Albe wrote:
> On Tue, 2021-03-30 at 17:57 +0800, Julien Rouhaud wrote:
> > 
> > Getting a consistent behavior shouldn't be hard, but I'm unsure which behavior
> > is actually correct.
> 
> I'd say that this is certainly wrong:
> 
> SELECT point('NaN','NaN') <@ polygon('(0,0),(1,0),(1,1),(0,0)');
> 
>  ?column? 
> ----------
>  t
> (1 row)

Yeah that's what I think too, but I wanted to have confirmation.



Re: Issue with point_ops and NaN

From
Tom Lane
Date:
Julien Rouhaud <rjuju123@gmail.com> writes:
> On Tue, Mar 30, 2021 at 02:47:05PM +0200, Laurenz Albe wrote:
>> I'd say that this is certainly wrong:
>> SELECT point('NaN','NaN') <@ polygon('(0,0),(1,0),(1,1),(0,0)');
>> 
>> ?column? 
>> ----------
>>  t
>>  (1 row)

> Yeah that's what I think too, but I wanted to have confirmation.

Agreed --- one could make an argument for either 'false' or NULL
result, but surely not 'true'.

I wonder if Horiguchi-san's patch [1] improves this case.

            regards, tom lane

[1] https://commitfest.postgresql.org/32/2710/



Re: Issue with point_ops and NaN

From
Kyotaro Horiguchi
Date:
At Wed, 31 Mar 2021 09:26:00 +0900, Michael Paquier <michael@paquier.xyz> wrote in 
> On Tue, Mar 30, 2021 at 11:39:40PM +0800, Julien Rouhaud wrote:
> > On Tue, Mar 30, 2021 at 11:02:32AM -0400, Tom Lane wrote:
> >> Agreed --- one could make an argument for either 'false' or NULL
> >> result, but surely not 'true'.
> > 
> > I would think that it should return NULL since it's not inside nor outside the
> > polygon, but I'm fine with false.
> 
> Yeah, this is trying to make an undefined point fit into a box that
> has a  definition, so "false" does not make sense to me either here as
> it implies that the point exists?  NULL seems adapted here.

Sounds reasonable.  The function may return NULL for other cases so
it's easily changed to NULL.

# But it's bothersome to cover all parallels..

Does anyone oppose to make the case NULL?  If no one objects, I'll do
that.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: Issue with point_ops and NaN

From
Kyotaro Horiguchi
Date:
At Wed, 31 Mar 2021 12:04:26 +0800, Julien Rouhaud <rjuju123@gmail.com> wrote in 
> On Tue, Mar 30, 2021 at 11:39:40PM +0800, Julien Rouhaud wrote:
> > On Tue, Mar 30, 2021 at 11:02:32AM -0400, Tom Lane wrote:
> > > Julien Rouhaud <rjuju123@gmail.com> writes:
> > > > On Tue, Mar 30, 2021 at 02:47:05PM +0200, Laurenz Albe wrote:
> > > >> I'd say that this is certainly wrong:
> > > >> SELECT point('NaN','NaN') <@ polygon('(0,0),(1,0),(1,1),(0,0)');
> > > >> 
> > > >> ?column? 
> > > >> ----------
> > > >>  t
> > > >>  (1 row)
> > > 
> > > > Yeah that's what I think too, but I wanted to have confirmation.
> > > 
> > > Agreed --- one could make an argument for either 'false' or NULL
> > > result, but surely not 'true'.
> > 
> > I would think that it should return NULL since it's not inside nor outside the
> > polygon, but I'm fine with false.
> > 
> > > I wonder if Horiguchi-san's patch [1] improves this case.
> > 
> > Oh I totally missed that patch :(
> > 
> > After a quick look I see this addition in point_inside():
> > 
> > +        /* NaN makes the point cannot be inside the polygon */
> > +        if (unlikely(isnan(x) || isnan(y)))
> > +            return 0;
> > 
> > So I would assume that it should fix this case too.  I'll check tomorrow.
> 
> I confirm that this patch fixes the issue, and after looking a bit more at the
> thread it's unsurprising since Jesse initially reported the exact same problem.
> 
> I'll try to review it as soon as I'll be done with my work duties.

Thanks! However, Michael's suggestion is worth considering.  What do
you think about makeing NaN-involved comparison return NULL?  If you
agree to that, I'll make a further change to the patch.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: Issue with point_ops and NaN

From
Kyotaro Horiguchi
Date:
At Wed, 31 Mar 2021 15:46:16 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in 
> At Wed, 31 Mar 2021 09:26:00 +0900, Michael Paquier <michael@paquier.xyz> wrote in 
> > On Tue, Mar 30, 2021 at 11:39:40PM +0800, Julien Rouhaud wrote:
> > > On Tue, Mar 30, 2021 at 11:02:32AM -0400, Tom Lane wrote:
> > >> Agreed --- one could make an argument for either 'false' or NULL
> > >> result, but surely not 'true'.
> > > 
> > > I would think that it should return NULL since it's not inside nor outside the
> > > polygon, but I'm fine with false.
> > 
> > Yeah, this is trying to make an undefined point fit into a box that
> > has a  definition, so "false" does not make sense to me either here as
> > it implies that the point exists?  NULL seems adapted here.
> 
> Sounds reasonable.  The function may return NULL for other cases so
> it's easily changed to NULL.
> 
> # But it's bothersome to cover all parallels..

Hmm. Many internal functions handles bool, which cannot handle the
case of NaN naturally.  In short, it's more invasive than expected.

> Does anyone oppose to make the case NULL?  If no one objects, I'll do
> that.

Mmm. I'd like to reduce from +1 to +0.7 or so, considering the amount
of needed work...

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: Issue with point_ops and NaN

From
Laurenz Albe
Date:
On Wed, 2021-03-31 at 15:48 +0900, Kyotaro Horiguchi wrote:
> > > > > > SELECT point('NaN','NaN') <@ polygon('(0,0),(1,0),(1,1),(0,0)');
> > > > > > ?column? 
> > > > > > ----------
> > > > > >   t
> > > > > >   (1 row)
> > > > 
> > > > Agreed --- one could make an argument for either 'false' or NULL
> > > > result, but surely not 'true'.
> 
> Thanks! However, Michael's suggestion is worth considering.  What do
> you think about makeing NaN-involved comparison return NULL?  If you
> agree to that, I'll make a further change to the patch.

If you think of "NaN" literally as "not a number", then FALSE would
make sense, since "not a number" implies "not a number between 0 and 1".

But since NaN is the result of operations like 0/0 or infinity - infinity,
NULL might be better.

So I'd opt for NULL too.

Yours,
Laurenz Albe




Re: Issue with point_ops and NaN

From
Kyotaro Horiguchi
Date:
At Wed, 31 Mar 2021 16:30:41 +0800, Julien Rouhaud <rjuju123@gmail.com> wrote in 
> On Wed, Mar 31, 2021 at 03:48:16PM +0900, Kyotaro Horiguchi wrote:
> > 
> > Thanks! However, Michael's suggestion is worth considering.  What do
> > you think about makeing NaN-involved comparison return NULL?  If you
> > agree to that, I'll make a further change to the patch.
> 
> As I mentioned in [1] I think that returning NULL would the right thing to do.
> But you mentioned elsewhere that it would need a lot more work to make the code
> work that way, so given that we're 7 days away from the feature freeze maybe
> returning false would be a better option.  One important thing to consider is

Agreed that it's a better option.

I have to change almost all boolean-returning functions to
tri-state-boolean ones. I'll give it try a bit futther.

> that we should consistently return NULL for similar cases, and having some
> discrepancy there would be way worse than returning false everywhere.

Sure.

> [1] https://www.postgresql.org/message-id/20210330153940.vmncwnmuw3qnpkfa@nol

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: Issue with point_ops and NaN

From
Kyotaro Horiguchi
Date:
At Wed, 31 Mar 2021 12:01:08 +0200, Laurenz Albe <laurenz.albe@cybertec.at> wrote in 
> On Wed, 2021-03-31 at 15:48 +0900, Kyotaro Horiguchi wrote:
> > > > > > > SELECT point('NaN','NaN') <@ polygon('(0,0),(1,0),(1,1),(0,0)');
> > > > > > > ?column? 
> > > > > > > ----------
> > > > > > >   t
> > > > > > >   (1 row)
> > > > > 
> > > > > Agreed --- one could make an argument for either 'false' or NULL
> > > > > result, but surely not 'true'.
> > 
> > Thanks! However, Michael's suggestion is worth considering.  What do
> > you think about makeing NaN-involved comparison return NULL?  If you
> > agree to that, I'll make a further change to the patch.
> 
> If you think of "NaN" literally as "not a number", then FALSE would
> make sense, since "not a number" implies "not a number between 0 and 1".
> 
> But since NaN is the result of operations like 0/0 or infinity - infinity,
> NULL might be better.
> 
> So I'd opt for NULL too.

Thanks.  Do you think it's acceptable that returning false instead of
NULL as an alternative behavior?

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center



Re: Issue with point_ops and NaN

From
Kyotaro Horiguchi
Date:
At Thu, 01 Apr 2021 09:34:40 +0900 (JST), Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote in 
> I have to change almost all boolean-returning functions to
> tri-state-boolean ones. I'll give it try a bit futther.

The attached is a rush work of that, on top of the (rebased version of
the) base patch.  Disregarding its uneffectiveness, it gives a rough
estimate of how large that would be and how that affects other parts.

Maybe one of the largest issue with that would be that GiST doesn't
seem to like NULL to be returned from comparison functions.


regression=# set enable_seqscan to off;
regression=# set enable_indexscan to on;
regression=# SELECT * FROM circle_tbl WHERE f1 && circle(point(1,-2), 1) ORDER BY area(f1);
ERROR:  function 0x9d7bf6 returned NULL
(function 0x9d7bf6 is box_overlap())

That seems like the reason not to make the functions tri-state.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index a2e798ff95..b9ff60f56b 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -79,7 +79,7 @@ static inline void point_add_point(Point *result, Point *pt1, Point *pt2);
 static inline void point_sub_point(Point *result, Point *pt1, Point *pt2);
 static inline void point_mul_point(Point *result, Point *pt1, Point *pt2);
 static inline void point_div_point(Point *result, Point *pt1, Point *pt2);
-static inline bool point_eq_point(Point *pt1, Point *pt2);
+static inline tsbool point_eq_point(Point *pt1, Point *pt2);
 static inline float8 point_dt(Point *pt1, Point *pt2);
 static inline float8 point_sl(Point *pt1, Point *pt2);
 static int    point_inside(Point *p, int npts, Point *plist);
@@ -88,18 +88,18 @@ static int    point_inside(Point *p, int npts, Point *plist);
 static inline void line_construct(LINE *result, Point *pt, float8 m);
 static inline float8 line_sl(LINE *line);
 static inline float8 line_invsl(LINE *line);
-static bool line_interpt_line(Point *result, LINE *l1, LINE *l2);
-static bool line_contain_point(LINE *line, Point *point);
+static tsbool line_interpt_line(Point *result, LINE *l1, LINE *l2);
+static tsbool line_contain_point(LINE *line, Point *point);
 static float8 line_closept_point(Point *result, LINE *line, Point *pt);
 
 /* Routines for line segments */
 static inline void statlseg_construct(LSEG *lseg, Point *pt1, Point *pt2);
 static inline float8 lseg_sl(LSEG *lseg);
 static inline float8 lseg_invsl(LSEG *lseg);
-static bool lseg_interpt_line(Point *result, LSEG *lseg, LINE *line);
-static bool lseg_interpt_lseg(Point *result, LSEG *l1, LSEG *l2);
+static tsbool lseg_interpt_line(Point *result, LSEG *lseg, LINE *line);
+static tsbool lseg_interpt_lseg(Point *result, LSEG *l1, LSEG *l2);
 static int    lseg_crossing(float8 x, float8 y, float8 px, float8 py);
-static bool lseg_contain_point(LSEG *lseg, Point *point);
+static tsbool lseg_contain_point(LSEG *lseg, Point *point);
 static float8 lseg_closept_point(Point *result, LSEG *lseg, Point *pt);
 static float8 lseg_closept_line(Point *result, LSEG *lseg, LINE *line);
 static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
@@ -107,14 +107,14 @@ static float8 lseg_closept_lseg(Point *result, LSEG *on_lseg, LSEG *to_lseg);
 /* Routines for boxes */
 static inline void box_construct(BOX *result, Point *pt1, Point *pt2);
 static void box_cn(Point *center, BOX *box);
-static bool box_ov(BOX *box1, BOX *box2);
+static tsbool box_ov(BOX *box1, BOX *box2);
 static float8 box_ar(BOX *box);
 static float8 box_ht(BOX *box);
 static float8 box_wd(BOX *box);
-static bool box_contain_point(BOX *box, Point *point);
-static bool box_contain_box(BOX *contains_box, BOX *contained_box);
-static bool box_contain_lseg(BOX *box, LSEG *lseg);
-static bool box_interpt_lseg(Point *result, BOX *box, LSEG *lseg);
+static tsbool box_contain_point(BOX *box, Point *point);
+static tsbool box_contain_box(BOX *contains_box, BOX *contained_box);
+static tsbool box_contain_lseg(BOX *box, LSEG *lseg);
+static tsbool box_interpt_lseg(Point *result, BOX *box, LSEG *lseg);
 static float8 box_closept_point(Point *result, BOX *box, Point *point);
 static float8 box_closept_lseg(Point *result, BOX *box, LSEG *lseg);
 
@@ -124,9 +124,9 @@ static float8 circle_ar(CIRCLE *circle);
 /* Routines for polygons */
 static void make_bound_box(POLYGON *poly);
 static void poly_to_circle(CIRCLE *result, POLYGON *poly);
-static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
-static bool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
-static bool plist_same(int npts, Point *p1, Point *p2);
+static tsbool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
+static tsbool poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly);
+static tsbool plist_same(int npts, Point *p1, Point *p2);
 static float8 dist_ppoly_internal(Point *pt, POLYGON *poly);
 
 /* Routines for encoding and decoding */
@@ -540,9 +540,13 @@ box_same(PG_FUNCTION_ARGS)
 {
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
+    tsbool        res1 = point_eq_point(&box1->high, &box2->high);
+    tsbool        res2 = point_eq_point(&box1->low, &box2->low);
 
-    PG_RETURN_BOOL(point_eq_point(&box1->high, &box2->high) &&
-                   point_eq_point(&box1->low, &box2->low));
+    if (res1 == TS_NULL || res2 == TS_NULL)
+        PG_RETURN_NULL();
+
+    PG_RETURN_BOOL(res1 && res2);
 }
 
 /*        box_overlap        -        does box1 overlap box2?
@@ -553,16 +557,16 @@ box_overlap(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(box_ov(box1, box2));
+    PG_RETURN_TSBOOL(box_ov(box1, box2));
 }
 
-static bool
+static tsbool
 box_ov(BOX *box1, BOX *box2)
 {
-    return (FPle(box1->low.x, box2->high.x) &&
-            FPle(box2->low.x, box1->high.x) &&
-            FPle(box1->low.y, box2->high.y) &&
-            FPle(box2->low.y, box1->high.y));
+    return (TS_AND4(FPTle(box1->low.x, box2->high.x),
+                    FPTle(box2->low.x, box1->high.x),
+                    FPTle(box1->low.y, box2->high.y),
+                    FPTle(box2->low.y, box1->high.y)));
 }
 
 /*        box_left        -        is box1 strictly left of box2?
@@ -573,7 +577,7 @@ box_left(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPlt(box1->high.x, box2->low.x));
+    PG_RETURN_TSBOOL(FPTlt(box1->high.x, box2->low.x));
 }
 
 /*        box_overleft    -        is the right edge of box1 at or left of
@@ -588,7 +592,7 @@ box_overleft(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPle(box1->high.x, box2->high.x));
+    PG_RETURN_TSBOOL(FPTle(box1->high.x, box2->high.x));
 }
 
 /*        box_right        -        is box1 strictly right of box2?
@@ -599,7 +603,7 @@ box_right(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPgt(box1->low.x, box2->high.x));
+    PG_RETURN_TSBOOL(FPTgt(box1->low.x, box2->high.x));
 }
 
 /*        box_overright    -        is the left edge of box1 at or right of
@@ -614,7 +618,7 @@ box_overright(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPge(box1->low.x, box2->low.x));
+    PG_RETURN_TSBOOL(FPTge(box1->low.x, box2->low.x));
 }
 
 /*        box_below        -        is box1 strictly below box2?
@@ -625,7 +629,7 @@ box_below(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPlt(box1->high.y, box2->low.y));
+    PG_RETURN_TSBOOL(FPTlt(box1->high.y, box2->low.y));
 }
 
 /*        box_overbelow    -        is the upper edge of box1 at or below
@@ -637,7 +641,7 @@ box_overbelow(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPle(box1->high.y, box2->high.y));
+    PG_RETURN_TSBOOL(FPTle(box1->high.y, box2->high.y));
 }
 
 /*        box_above        -        is box1 strictly above box2?
@@ -648,7 +652,7 @@ box_above(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPgt(box1->low.y, box2->high.y));
+    PG_RETURN_TSBOOL(FPTgt(box1->low.y, box2->high.y));
 }
 
 /*        box_overabove    -        is the lower edge of box1 at or above
@@ -660,7 +664,7 @@ box_overabove(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPge(box1->low.y, box2->low.y));
+    PG_RETURN_TSBOOL(FPTge(box1->low.y, box2->low.y));
 }
 
 /*        box_contained    -        is box1 contained by box2?
@@ -671,7 +675,7 @@ box_contained(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(box_contain_box(box2, box1));
+    PG_RETURN_TSBOOL(box_contain_box(box2, box1));
 }
 
 /*        box_contain        -        does box1 contain box2?
@@ -682,19 +686,19 @@ box_contain(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(box_contain_box(box1, box2));
+    PG_RETURN_TSBOOL(box_contain_box(box1, box2));
 }
 
 /*
  * Check whether the second box is in the first box or on its border
  */
-static bool
+static tsbool
 box_contain_box(BOX *contains_box, BOX *contained_box)
 {
-    return FPge(contains_box->high.x, contained_box->high.x) &&
-        FPle(contains_box->low.x, contained_box->low.x) &&
-        FPge(contains_box->high.y, contained_box->high.y) &&
-        FPle(contains_box->low.y, contained_box->low.y);
+    return TS_AND4(FPTge(contains_box->high.x, contained_box->high.x),
+                   FPTle(contains_box->low.x, contained_box->low.x),
+                   FPTge(contains_box->high.y, contained_box->high.y),
+                   FPTle(contains_box->low.y, contained_box->low.y));
 }
 
 
@@ -712,7 +716,7 @@ box_below_eq(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPle(box1->high.y, box2->low.y));
+    PG_RETURN_TSBOOL(FPTle(box1->high.y, box2->low.y));
 }
 
 Datum
@@ -721,7 +725,7 @@ box_above_eq(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPge(box1->low.y, box2->high.y));
+    PG_RETURN_TSBOOL(FPTge(box1->low.y, box2->high.y));
 }
 
 
@@ -734,7 +738,7 @@ box_lt(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPlt(box_ar(box1), box_ar(box2)));
+    PG_RETURN_TSBOOL(FPTlt(box_ar(box1), box_ar(box2)));
 }
 
 Datum
@@ -743,7 +747,7 @@ box_gt(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPgt(box_ar(box1), box_ar(box2)));
+    PG_RETURN_TSBOOL(FPTgt(box_ar(box1), box_ar(box2)));
 }
 
 Datum
@@ -752,7 +756,7 @@ box_eq(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPeq(box_ar(box1), box_ar(box2)));
+    PG_RETURN_TSBOOL(FPTeq(box_ar(box1), box_ar(box2)));
 }
 
 Datum
@@ -761,7 +765,7 @@ box_le(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPle(box_ar(box1), box_ar(box2)));
+    PG_RETURN_TSBOOL(FPTle(box_ar(box1), box_ar(box2)));
 }
 
 Datum
@@ -770,7 +774,7 @@ box_ge(PG_FUNCTION_ARGS)
     BOX           *box1 = PG_GETARG_BOX_P(0);
     BOX           *box2 = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(FPge(box_ar(box1), box_ar(box2)));
+    PG_RETURN_TSBOOL(FPTge(box_ar(box1), box_ar(box2)));
 }
 
 
@@ -981,7 +985,7 @@ line_in(PG_FUNCTION_ARGS)
     else
     {
         path_decode(s, true, 2, &lseg.p[0], &isopen, NULL, "line", str);
-        if (point_eq_point(&lseg.p[0], &lseg.p[1]))
+        if (point_eq_point(&lseg.p[0], &lseg.p[1]) == TS_TRUE)
             ereport(ERROR,
                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                      errmsg("invalid line specification: must be two distinct points")));
@@ -1105,7 +1109,7 @@ line_construct_pp(PG_FUNCTION_ARGS)
     LINE       *result = (LINE *) palloc(sizeof(LINE));
 
     /* NaNs are considered to be equal by point_eq_point */
-    if (point_eq_point(pt1, pt2))
+    if (point_eq_point(pt1, pt2) == TS_TRUE)
         ereport(ERROR,
                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                  errmsg("invalid line specification: must be two distinct points")));
@@ -1126,11 +1130,16 @@ line_intersect(PG_FUNCTION_ARGS)
     LINE       *l1 = PG_GETARG_LINE_P(0);
     LINE       *l2 = PG_GETARG_LINE_P(1);
     Point        xp;
+    tsbool        r;
 
-    if (line_interpt_line(&xp, l1, l2) && !isnan(xp.x) && !isnan(xp.y))
-        PG_RETURN_BOOL(true);
+    if ((r = line_interpt_line(&xp, l1, l2)) == TS_TRUE)
+    {
+        if (!isnan(xp.x) && !isnan(xp.y))
+            PG_RETURN_BOOL(true);
+        PG_RETURN_NULL();
+    }
     else
-        PG_RETURN_BOOL(false);
+        PG_RETURN_TSBOOL(r);
 }
 
 Datum
@@ -1139,7 +1148,7 @@ line_parallel(PG_FUNCTION_ARGS)
     LINE       *l1 = PG_GETARG_LINE_P(0);
     LINE       *l2 = PG_GETARG_LINE_P(1);
 
-    PG_RETURN_BOOL(!line_interpt_line(NULL, l1, l2));
+    PG_RETURN_TSBOOL(TS_NOT(line_interpt_line(NULL, l1, l2)));
 }
 
 Datum
@@ -1148,17 +1157,18 @@ line_perp(PG_FUNCTION_ARGS)
     LINE       *l1 = PG_GETARG_LINE_P(0);
     LINE       *l2 = PG_GETARG_LINE_P(1);
 
-    if (unlikely(isnan(l1->C) || isnan(l2->C)))
-        return false;
+    if (unlikely(isnan(l1->A) || isnan(l1->B) || isnan(l1->C) ||
+                 isnan(l2->A) || isnan(l2->B) || isnan(l2->C)))
+        PG_RETURN_NULL();
 
     if (FPzero(l1->A))
-        PG_RETURN_BOOL(FPzero(l2->B) && !isnan(l1->B) && !isnan(l2->A));
+        PG_RETURN_BOOL(FPzero(l2->B));
     if (FPzero(l2->A))
-        PG_RETURN_BOOL(FPzero(l1->B) && !isnan(l2->B) && !isnan(l1->A));
+        PG_RETURN_BOOL(FPzero(l1->B));
     if (FPzero(l1->B))
-        PG_RETURN_BOOL(FPzero(l2->A) && !isnan(l1->A) && !isnan(l2->B));
+        PG_RETURN_BOOL(FPzero(l2->A));
     if (FPzero(l2->B))
-        PG_RETURN_BOOL(FPzero(l1->A) && !isnan(l2->A) && !isnan(l1->B));
+        PG_RETURN_BOOL(FPzero(l1->A));
 
     PG_RETURN_BOOL(FPeq(float8_div(float8_mul(l1->A, l2->A),
                                    float8_mul(l1->B, l2->B)), -1.0));
@@ -1177,7 +1187,10 @@ line_horizontal(PG_FUNCTION_ARGS)
 {
     LINE       *line = PG_GETARG_LINE_P(0);
 
-    PG_RETURN_BOOL(FPzero(line->A) && !isnan(line->B) && !isnan(line->C));
+    if (!isnan(line->B) && !isnan(line->C))
+        PG_RETURN_TSBOOL(FPzero(line->A));
+
+    PG_RETURN_NULL();
 }
 
 
@@ -1191,14 +1204,10 @@ line_eq(PG_FUNCTION_ARGS)
     LINE       *l2 = PG_GETARG_LINE_P(1);
     float8        ratio;
 
-    /* If any NaNs are involved, insist on exact equality */
+    /* If any NaNs are involved, the two cannot be equal */
     if (unlikely(isnan(l1->A) || isnan(l1->B) || isnan(l1->C) ||
                  isnan(l2->A) || isnan(l2->B) || isnan(l2->C)))
-    {
-        PG_RETURN_BOOL(float8_eq(l1->A, l2->A) &&
-                       float8_eq(l1->B, l2->B) &&
-                       float8_eq(l1->C, l2->C));
-    }
+        PG_RETURN_NULL();
 
     /* Otherwise, lines whose parameters are proportional are the same */
     if (!FPzero(l2->A))
@@ -1281,7 +1290,7 @@ line_distance(PG_FUNCTION_ARGS)
     Point        xp;
     float8        ratio;
 
-    if (line_interpt_line(&xp, l1, l2)) /* intersecting? */
+    if (line_interpt_line(&xp, l1, l2) == TS_TRUE) /* intersecting? */
     {
         /* return NaN if NaN is involved */
         if (isnan(xp.x) || isnan(xp.y))
@@ -1316,7 +1325,7 @@ line_interpt(PG_FUNCTION_ARGS)
 
     result = (Point *) palloc(sizeof(Point));
 
-    if (!line_interpt_line(result, l1, l2) ||
+    if (line_interpt_line(result, l1, l2) != TS_TRUE ||
         isnan(result->x) || isnan(result->y))
     {
         pfree(result);
@@ -1340,17 +1349,24 @@ line_interpt(PG_FUNCTION_ARGS)
  * point would have NaN coordinates.  We shouldn't return false in this case
  * because that would mean the lines are parallel.
  */
-static bool
+static tsbool
 line_interpt_line(Point *result, LINE *l1, LINE *l2)
 {
     float8        x,
                 y;
+    tsbool        r;
 
-    if (!FPzero(l1->B))
+    if ((r = FPTzero(l1->B)) == TS_FALSE)
     {
         /* l1 is not virtucal */
-        if (FPeq(l2->A, float8_mul(l1->A, float8_div(l2->B, l1->B))))
-            return false;
+        if ((r = FPTeq(l2->A, float8_mul(l1->A, float8_div(l2->B, l1->B))))
+            != TS_FALSE)
+        {
+            if (r == TS_TRUE)
+                return TS_FALSE;
+            else
+                return TS_NULL;
+        }
 
         x = float8_div(float8_mi(float8_mul(l1->B, l2->C),
                                  float8_mul(l2->B, l1->C)),
@@ -1367,7 +1383,7 @@ line_interpt_line(Point *result, LINE *l1, LINE *l2)
                        float8_mi(float8_mul(l2->A, l1->B),
                                  float8_mul(l1->A, l2->B)));
     }
-    else if (!FPzero(l2->B))
+    else if ((r = FPzero(l2->B)) == TS_FALSE)
     {
         /* l2 is not virtical */
         /*
@@ -1380,13 +1396,17 @@ line_interpt_line(Point *result, LINE *l1, LINE *l2)
          * When l2->A is zero, y is determined independently from x even if it
          * is Inf.
          */
-        if (FPzero(l2->A))
+        if ((r = FPzero(l2->A)) == TS_TRUE)
             y = -float8_div(l2->C, l2->B);
-        else
+        else if (r == TS_FALSE)
             y = float8_div(-float8_pl(float8_mul(l2->A, x), l2->C), l2->B);
+        else
+            return TS_NULL;
     }
+    else if (r != TS_NULL)
+        return TS_FALSE;
     else
-        return false;
+        return TS_NULL;
 
     /* On some platforms, the preceding expressions tend to produce -0. */
     if (x == 0.0)
@@ -1397,7 +1417,7 @@ line_interpt_line(Point *result, LINE *l1, LINE *l2)
     if (result != NULL)
         point_construct(result, x, y);
 
-    return true;
+    return TS_TRUE;
 }
 
 
@@ -1704,6 +1724,7 @@ path_inter(PG_FUNCTION_ARGS)
                 j;
     LSEG        seg1,
                 seg2;
+    tsbool        r;
 
     Assert(p1->npts > 0 && p2->npts > 0);
 
@@ -1716,6 +1737,7 @@ path_inter(PG_FUNCTION_ARGS)
         b1.low.x = float8_min_nan(p1->p[i].x, b1.low.x);
         b1.low.y = float8_min_nan(p1->p[i].y, b1.low.y);
     }
+
     b2.high.x = b2.low.x = p2->p[0].x;
     b2.high.y = b2.low.y = p2->p[0].y;
     for (i = 1; i < p2->npts; i++)
@@ -1725,8 +1747,8 @@ path_inter(PG_FUNCTION_ARGS)
         b2.low.x = float8_min_nan(p2->p[i].x, b2.low.x);
         b2.low.y = float8_min_nan(p2->p[i].y, b2.low.y);
     }
-    if (!box_ov(&b1, &b2))
-        PG_RETURN_BOOL(false);
+    if ((r = box_ov(&b1, &b2)) != TS_TRUE)
+        PG_RETURN_TSBOOL(r);
 
     /* pairwise check lseg intersections */
     for (i = 0; i < p1->npts; i++)
@@ -2004,8 +2026,12 @@ point_eq(PG_FUNCTION_ARGS)
 {
     Point       *pt1 = PG_GETARG_POINT_P(0);
     Point       *pt2 = PG_GETARG_POINT_P(1);
+    tsbool        res = point_eq_point(pt1, pt2);
 
-    PG_RETURN_BOOL(point_eq_point(pt1, pt2));
+    if (res == TS_NULL)
+        PG_RETURN_NULL();
+
+    PG_RETURN_BOOL(res);
 }
 
 Datum
@@ -2013,23 +2039,22 @@ point_ne(PG_FUNCTION_ARGS)
 {
     Point       *pt1 = PG_GETARG_POINT_P(0);
     Point       *pt2 = PG_GETARG_POINT_P(1);
+    tsbool        res = point_eq_point(pt1, pt2);
 
-    PG_RETURN_BOOL(!point_eq_point(pt1, pt2));
+    if (res == TS_NULL)
+        PG_RETURN_NULL();
+
+    PG_RETURN_BOOL(!res);
 }
 
 
 /*
  * Check whether the two points are the same
  */
-static inline bool
+static inline tsbool
 point_eq_point(Point *pt1, Point *pt2)
 {
-    /* If any NaNs are involved, insist on exact equality */
-    if (unlikely(isnan(pt1->x) || isnan(pt1->y) ||
-                 isnan(pt2->x) || isnan(pt2->y)))
-        return (float8_eq(pt1->x, pt2->x) && float8_eq(pt1->y, pt2->y));
-
-    return (FPeq(pt1->x, pt2->x) && FPeq(pt1->y, pt2->y));
+    return TS_AND2(FPTeq(pt1->x, pt2->x), FPTeq(pt1->y, pt2->y));
 }
 
 
@@ -2070,6 +2095,7 @@ point_slope(PG_FUNCTION_ARGS)
 static inline float8
 point_sl(Point *pt1, Point *pt2)
 {
+    /* NaN doesn't equal to NaN, so don't bother using a tri-state value */
     if (FPeq(pt1->x, pt2->x))
     {
         if (unlikely(isnan(pt1->y) || isnan(pt2->y)))
@@ -2096,6 +2122,7 @@ point_sl(Point *pt1, Point *pt2)
 static inline float8
 point_invsl(Point *pt1, Point *pt2)
 {
+    /* NaN doesn't equal to NaN, so don't bother using a tri-state value */
     if (FPeq(pt1->x, pt2->x))
     {
         if (unlikely(isnan(pt1->y) || isnan(pt2->y)))
@@ -2254,7 +2281,7 @@ lseg_intersect(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(lseg_interpt_lseg(NULL, l1, l2));
+    PG_RETURN_TSBOOL(lseg_interpt_lseg(NULL, l1, l2));
 }
 
 
@@ -2264,7 +2291,7 @@ lseg_parallel(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(FPeq(lseg_sl(l1), lseg_sl(l2)));
+    PG_RETURN_TSBOOL(FPTeq(lseg_sl(l1), lseg_sl(l2)));
 }
 
 /*
@@ -2276,7 +2303,7 @@ lseg_perp(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(FPeq(lseg_sl(l1), lseg_invsl(l2)));
+    PG_RETURN_TSBOOL(FPTeq(lseg_sl(l1), lseg_invsl(l2)));
 }
 
 Datum
@@ -2284,7 +2311,7 @@ lseg_vertical(PG_FUNCTION_ARGS)
 {
     LSEG       *lseg = PG_GETARG_LSEG_P(0);
 
-    PG_RETURN_BOOL(FPeq(lseg->p[0].x, lseg->p[1].x));
+    PG_RETURN_TSBOOL(FPeq(lseg->p[0].x, lseg->p[1].x));
 }
 
 Datum
@@ -2292,7 +2319,7 @@ lseg_horizontal(PG_FUNCTION_ARGS)
 {
     LSEG       *lseg = PG_GETARG_LSEG_P(0);
 
-    PG_RETURN_BOOL(FPeq(lseg->p[0].y, lseg->p[1].y));
+    PG_RETURN_TSBOOL(FPTeq(lseg->p[0].y, lseg->p[1].y));
 }
 
 
@@ -2302,8 +2329,8 @@ lseg_eq(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(point_eq_point(&l1->p[0], &l2->p[0]) &&
-                   point_eq_point(&l1->p[1], &l2->p[1]));
+    PG_RETURN_TSBOOL(TS_AND2(point_eq_point(&l1->p[0], &l2->p[0]),
+                             point_eq_point(&l1->p[1], &l2->p[1])));
 }
 
 Datum
@@ -2312,8 +2339,9 @@ lseg_ne(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(!point_eq_point(&l1->p[0], &l2->p[0]) ||
-                   !point_eq_point(&l1->p[1], &l2->p[1]));
+    PG_RETURN_TSBOOL(TS_OR2(
+                         TS_NOT(point_eq_point(&l1->p[0], &l2->p[0])),
+                         TS_NOT(point_eq_point(&l1->p[1], &l2->p[1]))));
 }
 
 Datum
@@ -2322,8 +2350,8 @@ lseg_lt(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(FPlt(point_dt(&l1->p[0], &l1->p[1]),
-                        point_dt(&l2->p[0], &l2->p[1])));
+    PG_RETURN_TSBOOL(FPTlt(point_dt(&l1->p[0], &l1->p[1]),
+                           point_dt(&l2->p[0], &l2->p[1])));
 }
 
 Datum
@@ -2332,8 +2360,8 @@ lseg_le(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(FPle(point_dt(&l1->p[0], &l1->p[1]),
-                        point_dt(&l2->p[0], &l2->p[1])));
+    PG_RETURN_TSBOOL(FPTle(point_dt(&l1->p[0], &l1->p[1]),
+                           point_dt(&l2->p[0], &l2->p[1])));
 }
 
 Datum
@@ -2342,8 +2370,8 @@ lseg_gt(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(FPgt(point_dt(&l1->p[0], &l1->p[1]),
-                        point_dt(&l2->p[0], &l2->p[1])));
+    PG_RETURN_TSBOOL(FPTgt(point_dt(&l1->p[0], &l1->p[1]),
+                           point_dt(&l2->p[0], &l2->p[1])));
 }
 
 Datum
@@ -2352,8 +2380,8 @@ lseg_ge(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(FPge(point_dt(&l1->p[0], &l1->p[1]),
-                        point_dt(&l2->p[0], &l2->p[1])));
+    PG_RETURN_TSBOOL(FPTge(point_dt(&l1->p[0], &l1->p[1]),
+                           point_dt(&l2->p[0], &l2->p[1])));
 }
 
 
@@ -2398,27 +2426,28 @@ lseg_center(PG_FUNCTION_ARGS)
  * This function is almost perfectly symmetric, even though it doesn't look
  * like it.  See lseg_interpt_line() for the other half of it.
  */
-static bool
+static tsbool
 lseg_interpt_lseg(Point *result, LSEG *l1, LSEG *l2)
 {
     Point        interpt;
     LINE        tmp;
+    tsbool        r;
 
     line_construct(&tmp, &l2->p[0], lseg_sl(l2));
-    if (!lseg_interpt_line(&interpt, l1, &tmp))
-        return false;
+    if ((r = lseg_interpt_line(&interpt, l1, &tmp)) != TS_TRUE)
+        return r;
 
     /*
      * If the line intersection point isn't within l2, there is no valid
      * segment intersection point at all.
      */
-    if (!lseg_contain_point(l2, &interpt))
-        return false;
+    if ((r = lseg_contain_point(l2, &interpt)) != TS_TRUE)
+        return r;
 
     if (result != NULL)
         *result = interpt;
 
-    return true;
+    return TS_TRUE;
 }
 
 Datum
@@ -2427,10 +2456,11 @@ lseg_interpt(PG_FUNCTION_ARGS)
     LSEG       *l1 = PG_GETARG_LSEG_P(0);
     LSEG       *l2 = PG_GETARG_LSEG_P(1);
     Point       *result;
+    tsbool        r;
 
     result = (Point *) palloc(sizeof(Point));
 
-    if (!lseg_interpt_lseg(result, l1, l2))
+    if ((r = lseg_interpt_lseg(result, l1, l2)) != TS_TRUE)
         PG_RETURN_NULL();
     PG_RETURN_POINT_P(result);
 }
@@ -2740,8 +2770,13 @@ dist_ppoly_internal(Point *pt, POLYGON *poly)
     float8        d;
     int            i;
     LSEG        seg;
+    int            inside;
 
-    if (point_inside(pt, poly->npts, poly->p) != 0)
+    inside = point_inside(pt, poly->npts, poly->p);
+    if (inside == -1)
+        return get_float8_nan();
+
+    if (inside != 0)
         return 0.0;
 
     /* initialize distance with segment between first and last points */
@@ -2780,11 +2815,12 @@ dist_ppoly_internal(Point *pt, POLYGON *poly)
  * Return whether the line segment intersect with the line. If *result is not
  * NULL, it is set to the intersection point.
  */
-static bool
+static tsbool
 lseg_interpt_line(Point *result, LSEG *lseg, LINE *line)
 {
     Point        interpt;
     LINE        tmp;
+    tsbool        r;
 
     /*
      * First, we promote the line segment to a line, because we know how to
@@ -2792,32 +2828,40 @@ lseg_interpt_line(Point *result, LSEG *lseg, LINE *line)
      * intersection point, we are done.
      */
     line_construct(&tmp, &lseg->p[0], lseg_sl(lseg));
-    if (!line_interpt_line(&interpt, &tmp, line) ||
-        unlikely(isnan(interpt.x) || isnan(interpt.y)))
-        return false;
+    if ((r = line_interpt_line(&interpt, &tmp, line)) != TS_TRUE)
+        return r;
+
+    if (unlikely(isnan(interpt.x) || isnan(interpt.y)))
+        return TS_FALSE;
 
     /*
      * Then, we check whether the intersection point is actually on the line
      * segment.
      */
-    if (!lseg_contain_point(lseg, &interpt))
-        return false;
+    if ((r = lseg_contain_point(lseg, &interpt)) != TS_TRUE)
+        return r;
+
     if (result != NULL)
     {
+        tsbool    r;
+
         /*
          * If there is an intersection, then check explicitly for matching
          * endpoints since there may be rounding effects with annoying LSB
          * residue.
          */
-        if (point_eq_point(&lseg->p[0], &interpt))
+        if ((r = point_eq_point(&lseg->p[0], &interpt)) == TS_TRUE)
             *result = lseg->p[0];
-        else if (point_eq_point(&lseg->p[1], &interpt))
+        else if (r == TS_FALSE &&
+                 (r = point_eq_point(&lseg->p[1], &interpt)) == TS_TRUE)
             *result = lseg->p[1];
-        else
+        else if (r == TS_FALSE)
             *result = interpt;
+        else
+            return r;
     }
 
-    return true;
+    return TS_TRUE;
 }
 
 /*---------------------------------------------------------------------
@@ -3178,11 +3222,14 @@ close_sl(PG_FUNCTION_ARGS)
     Point       *result;
     float8        d1,
                 d2;
+    tsbool        r;
 
     result = (Point *) palloc(sizeof(Point));
 
-    if (lseg_interpt_line(result, lseg, line))
+    if ((r = lseg_interpt_line(result, lseg, line)) == TS_TRUE)
         PG_RETURN_POINT_P(result);
+    else if (r == TS_NULL)
+        PG_RETURN_NULL;
 
     d1 = line_closept_point(NULL, line, &lseg->p[0]);
     d2 = line_closept_point(NULL, line, &lseg->p[1]);
@@ -3219,9 +3266,12 @@ lseg_closept_line(Point *result, LSEG *lseg, LINE *line)
 {
     float8        dist1,
                 dist2;
+    tsbool        r;
 
-    if (lseg_interpt_line(result, lseg, line))
+    if ((r = lseg_interpt_line(result, lseg, line)) == TS_TRUE)
         return 0.0;
+    else if (r == TS_NULL)
+        return get_float8_nan();
 
     dist1 = line_closept_point(NULL, line, &lseg->p[0]);
     dist2 = line_closept_point(NULL, line, &lseg->p[1]);
@@ -3365,7 +3415,7 @@ close_lb(PG_FUNCTION_ARGS)
 /*
  *        Does the point satisfy the equation?
  */
-static bool
+static tsbool
 line_contain_point(LINE *line, Point *point)
 {
     /*
@@ -3379,7 +3429,7 @@ line_contain_point(LINE *line, Point *point)
         Assert(line->B != 0.0);
 
         /* inf == inf here */
-        return FPeq(point->y, -line->C / line->B);
+        return FPTeq(point->y, -line->C / line->B);
     }
     else if (line->B == 0.0)
     {
@@ -3387,13 +3437,13 @@ line_contain_point(LINE *line, Point *point)
         Assert(line->A != 0.0);
 
         /* inf == inf here */
-        return FPeq(point->x, -line->C / line->A);
+        return FPTeq(point->x, -line->C / line->A);
     }
 
-    return FPzero(float8_pl(
-                      float8_pl(float8_mul(line->A, point->x),
-                                float8_mul(line->B, point->y)),
-                      line->C));
+    return FPTzero(float8_pl(
+                       float8_pl(float8_mul(line->A, point->x),
+                                 float8_mul(line->B, point->y)),
+                       line->C));
 }
 
 Datum
@@ -3402,7 +3452,7 @@ on_pl(PG_FUNCTION_ARGS)
     Point       *pt = PG_GETARG_POINT_P(0);
     LINE       *line = PG_GETARG_LINE_P(1);
 
-    PG_RETURN_BOOL(line_contain_point(line, pt));
+    PG_RETURN_TSBOOL(line_contain_point(line, pt));
 }
 
 
@@ -3410,12 +3460,12 @@ on_pl(PG_FUNCTION_ARGS)
  *        Determine colinearity by detecting a triangle inequality.
  * This algorithm seems to behave nicely even with lsb residues - tgl 1997-07-09
  */
-static bool
+static tsbool
 lseg_contain_point(LSEG *lseg, Point *pt)
 {
-    return FPeq(point_dt(pt, &lseg->p[0]) +
-                point_dt(pt, &lseg->p[1]),
-                point_dt(&lseg->p[0], &lseg->p[1]));
+    return FPTeq(point_dt(pt, &lseg->p[0]) +
+                 point_dt(pt, &lseg->p[1]),
+                 point_dt(&lseg->p[0], &lseg->p[1]));
 }
 
 Datum
@@ -3424,18 +3474,25 @@ on_ps(PG_FUNCTION_ARGS)
     Point       *pt = PG_GETARG_POINT_P(0);
     LSEG       *lseg = PG_GETARG_LSEG_P(1);
 
-    PG_RETURN_BOOL(lseg_contain_point(lseg, pt));
+    PG_RETURN_TSBOOL(lseg_contain_point(lseg, pt));
 }
 
 
 /*
  * Check whether the point is in the box or on its border
  */
-static bool
+static tsbool
 box_contain_point(BOX *box, Point *point)
 {
-    return box->high.x >= point->x && box->low.x <= point->x &&
-        box->high.y >= point->y && box->low.y <= point->y;
+    if (box->high.x >= point->x && box->low.x <= point->x &&
+        box->high.y >= point->y && box->low.y <= point->y)
+        return TS_TRUE;
+    else if (!isnan(box->high.x) && !isnan(box->high.y) &&
+             !isnan(box->low.x) && !isnan(box->low.y) &&
+             !isnan(point->x) && !isnan(point->y))
+        return TS_FALSE;
+
+    return TS_NULL;
 }
 
 Datum
@@ -3444,7 +3501,7 @@ on_pb(PG_FUNCTION_ARGS)
     Point       *pt = PG_GETARG_POINT_P(0);
     BOX           *box = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(box_contain_point(box, pt));
+    PG_RETURN_TSBOOL(box_contain_point(box, pt));
 }
 
 Datum
@@ -3453,7 +3510,7 @@ box_contain_pt(PG_FUNCTION_ARGS)
     BOX           *box = PG_GETARG_BOX_P(0);
     Point       *pt = PG_GETARG_POINT_P(1);
 
-    PG_RETURN_BOOL(box_contain_point(box, pt));
+    PG_RETURN_TSBOOL(box_contain_point(box, pt));
 }
 
 /* on_ppath -
@@ -3476,24 +3533,38 @@ on_ppath(PG_FUNCTION_ARGS)
                 n;
     float8        a,
                 b;
+    int            inside;
 
     /*-- OPEN --*/
     if (!path->closed)
     {
+        tsbool r;
+
         n = path->npts - 1;
         a = point_dt(pt, &path->p[0]);
         for (i = 0; i < n; i++)
         {
             b = point_dt(pt, &path->p[i + 1]);
-            if (FPeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1])))
-                PG_RETURN_BOOL(true);
+            r = FPTeq(float8_pl(a, b), point_dt(&path->p[i], &path->p[i + 1]));
+            if (r != TS_FALSE)
+                PG_RETURN_TSBOOL(r);
             a = b;
         }
+        /* See the PG_RETURN_BOOL at the end of this function */
         PG_RETURN_BOOL(false);
     }
 
     /*-- CLOSED --*/
-    PG_RETURN_BOOL(point_inside(pt, path->npts, path->p) != 0);
+    inside = point_inside(pt, path->npts, path->p);
+
+    if (inside < 0)
+        PG_RETURN_NULL();
+
+    /*
+     * PG_RETURN_BOOL is compatible and faster than PG_RETURN_TSBOOL when the
+     * value is guaranteed to be in bool.
+     */
+    PG_RETURN_BOOL(inside != 0);
 }
 
 
@@ -3508,8 +3579,8 @@ on_sl(PG_FUNCTION_ARGS)
     LSEG       *lseg = PG_GETARG_LSEG_P(0);
     LINE       *line = PG_GETARG_LINE_P(1);
 
-    PG_RETURN_BOOL(line_contain_point(line, &lseg->p[0]) &&
-                   line_contain_point(line, &lseg->p[1]));
+    PG_RETURN_TSBOOL(TS_AND2(line_contain_point(line, &lseg->p[0]),
+                             line_contain_point(line, &lseg->p[1])));
 }
 
 
@@ -3518,11 +3589,11 @@ on_sl(PG_FUNCTION_ARGS)
  *
  * It is, if both of its points are in the box or on its border.
  */
-static bool
+static tsbool
 box_contain_lseg(BOX *box, LSEG *lseg)
 {
-    return box_contain_point(box, &lseg->p[0]) &&
-        box_contain_point(box, &lseg->p[1]);
+    return TS_AND2(box_contain_point(box, &lseg->p[0]),
+                   box_contain_point(box, &lseg->p[1]));
 }
 
 Datum
@@ -3531,7 +3602,7 @@ on_sb(PG_FUNCTION_ARGS)
     LSEG       *lseg = PG_GETARG_LSEG_P(0);
     BOX           *box = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(box_contain_lseg(box, lseg));
+    PG_RETURN_TSBOOL(box_contain_lseg(box, lseg));
 }
 
 /*---------------------------------------------------------------------
@@ -3545,7 +3616,7 @@ inter_sl(PG_FUNCTION_ARGS)
     LSEG       *lseg = PG_GETARG_LSEG_P(0);
     LINE       *line = PG_GETARG_LINE_P(1);
 
-    PG_RETURN_BOOL(lseg_interpt_line(NULL, lseg, line));
+    PG_RETURN_TSBOOL(lseg_interpt_line(NULL, lseg, line));
 }
 
 
@@ -3564,12 +3635,13 @@ inter_sl(PG_FUNCTION_ARGS)
  * Optimize for non-intersection by checking for box intersection first.
  * - thomas 1998-01-30
  */
-static bool
+static tsbool
 box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
 {
     BOX            lbox;
     LSEG        bseg;
     Point        point;
+    tsbool        r;
 
     lbox.low.x = float8_min(lseg->p[0].x, lseg->p[1].x);
     lbox.low.y = float8_min(lseg->p[0].y, lseg->p[1].y);
@@ -3577,8 +3649,8 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
     lbox.high.y = float8_max(lseg->p[0].y, lseg->p[1].y);
 
     /* nothing close to overlap? then not going to intersect */
-    if (!box_ov(&lbox, box))
-        return false;
+    if ((r = box_ov(&lbox, box)) != TS_TRUE)
+        return r;
 
     if (result != NULL)
     {
@@ -3587,30 +3659,30 @@ box_interpt_lseg(Point *result, BOX *box, LSEG *lseg)
     }
 
     /* an endpoint of segment is inside box? then clearly intersects */
-    if (box_contain_point(box, &lseg->p[0]) ||
-        box_contain_point(box, &lseg->p[1]))
-        return true;
+    if ((r = TS_OR2(box_contain_point(box, &lseg->p[0]),
+                    box_contain_point(box, &lseg->p[1]))) != TS_FALSE)
+        return r;
 
     /* pairwise check lseg intersections */
     point.x = box->low.x;
     point.y = box->high.y;
     statlseg_construct(&bseg, &box->low, &point);
-    if (lseg_interpt_lseg(NULL, &bseg, lseg))
-        return true;
+    if ((r = lseg_interpt_lseg(NULL, &bseg, lseg)) != TS_FALSE)
+        return r;
 
     statlseg_construct(&bseg, &box->high, &point);
-    if (lseg_interpt_lseg(NULL, &bseg, lseg))
-        return true;
+    if ((r = lseg_interpt_lseg(NULL, &bseg, lseg)) != TS_FALSE)
+        return r;
 
     point.x = box->high.x;
     point.y = box->low.y;
     statlseg_construct(&bseg, &box->low, &point);
-    if (lseg_interpt_lseg(NULL, &bseg, lseg))
-        return true;
+    if ((r = lseg_interpt_lseg(NULL, &bseg, lseg)) != TS_FALSE)
+        return r;
 
     statlseg_construct(&bseg, &box->high, &point);
-    if (lseg_interpt_lseg(NULL, &bseg, lseg))
-        return true;
+    if ((r = lseg_interpt_lseg(NULL, &bseg, lseg)) != TS_FALSE)
+        return r;
 
     /* if we dropped through, no two segs intersected */
     return false;
@@ -3622,7 +3694,7 @@ inter_sb(PG_FUNCTION_ARGS)
     LSEG       *lseg = PG_GETARG_LSEG_P(0);
     BOX           *box = PG_GETARG_BOX_P(1);
 
-    PG_RETURN_BOOL(box_interpt_lseg(NULL, box, lseg));
+    PG_RETURN_TSBOOL(box_interpt_lseg(NULL, box, lseg));
 }
 
 
@@ -3637,6 +3709,7 @@ inter_lb(PG_FUNCTION_ARGS)
     LSEG        bseg;
     Point        p1,
                 p2;
+    tsbool        r;
 
     /* pairwise check lseg intersections */
     p1.x = box->low.x;
@@ -3644,25 +3717,28 @@ inter_lb(PG_FUNCTION_ARGS)
     p2.x = box->low.x;
     p2.y = box->high.y;
     statlseg_construct(&bseg, &p1, &p2);
-    if (lseg_interpt_line(NULL, &bseg, line))
-        PG_RETURN_BOOL(true);
+    if ((r = lseg_interpt_line(NULL, &bseg, line)) != TS_FALSE)
+        PG_RETURN_TSBOOL(r);
     p1.x = box->high.x;
     p1.y = box->high.y;
     statlseg_construct(&bseg, &p1, &p2);
-    if (lseg_interpt_line(NULL, &bseg, line))
-        PG_RETURN_BOOL(true);
+    if ((r = lseg_interpt_line(NULL, &bseg, line)) != TS_FALSE)
+        PG_RETURN_TSBOOL(r);
     p2.x = box->high.x;
     p2.y = box->low.y;
     statlseg_construct(&bseg, &p1, &p2);
-    if (lseg_interpt_line(NULL, &bseg, line))
-        PG_RETURN_BOOL(true);
+    if ((r = lseg_interpt_line(NULL, &bseg, line)) != TS_FALSE)
+        PG_RETURN_TSBOOL(r);
     p1.x = box->low.x;
     p1.y = box->low.y;
     statlseg_construct(&bseg, &p1, &p2);
-    if (lseg_interpt_line(NULL, &bseg, line))
-        PG_RETURN_BOOL(true);
+    if ((r = lseg_interpt_line(NULL, &bseg, line)) != TS_FALSE)
+        PG_RETURN_TSBOOL(r);
 
-    /* if we dropped through, no intersection */
+    /*
+     * if we dropped through, no intersection "false" doesn't need
+     * PG_RETURN_TSBOOL()
+     */
     PG_RETURN_BOOL(false);
 }
 
@@ -3844,6 +3920,9 @@ poly_left(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.high.x < polyb->boundbox.low.x;
 
     /*
@@ -3867,6 +3946,9 @@ poly_overleft(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.high.x <= polyb->boundbox.high.x;
 
     /*
@@ -3890,6 +3972,9 @@ poly_right(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.low.x > polyb->boundbox.high.x;
 
     /*
@@ -3913,6 +3998,9 @@ poly_overright(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.low.x >= polyb->boundbox.low.x;
 
     /*
@@ -3936,6 +4024,9 @@ poly_below(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.high.y < polyb->boundbox.low.y;
 
     /*
@@ -3959,6 +4050,9 @@ poly_overbelow(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.high.y <= polyb->boundbox.high.y;
 
     /*
@@ -3982,6 +4076,9 @@ poly_above(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.low.y > polyb->boundbox.high.y;
 
     /*
@@ -4005,6 +4102,9 @@ poly_overabove(PG_FUNCTION_ARGS)
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
     bool        result;
 
+    if (isnan(polya->boundbox.high.x) || isnan(polyb->boundbox.high.x))
+        PG_RETURN_NULL();
+
     result = polya->boundbox.low.y >= polyb->boundbox.low.y;
 
     /*
@@ -4023,16 +4123,20 @@ poly_overabove(PG_FUNCTION_ARGS)
  * Check all points for matches in both forward and reverse
  *    direction since polygons are non-directional and are
  *    closed shapes.
+ *
+ * XXX: returns TS_FALSE when the two polygons consists of
+ * different number of points even if any of the points were
+ * NaN.  It might be thewrong defintion.
  *-------------------------------------------------------*/
 Datum
 poly_same(PG_FUNCTION_ARGS)
 {
     POLYGON    *polya = PG_GETARG_POLYGON_P(0);
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
-    bool        result;
+    tsbool        result;
 
     if (polya->npts != polyb->npts)
-        result = false;
+        result = TS_FALSE;
     else
         result = plist_same(polya->npts, polya->p, polyb->p);
 
@@ -4053,13 +4157,16 @@ poly_overlap(PG_FUNCTION_ARGS)
 {
     POLYGON    *polya = PG_GETARG_POLYGON_P(0);
     POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
-    bool        result;
+    tsbool        result;
 
     Assert(polya->npts > 0 && polyb->npts > 0);
 
     /* Quick check by bounding box */
     result = box_ov(&polya->boundbox, &polyb->boundbox);
 
+    if (result == TS_NULL)
+        PG_RETURN_NULL();
+
     /*
      * Brute-force algorithm - try to find intersected edges, if so then
      * polygons are overlapped else check is one polygon inside other or not
@@ -4074,9 +4181,9 @@ poly_overlap(PG_FUNCTION_ARGS)
 
         /* Init first of polya's edge with last point */
         sa.p[0] = polya->p[polya->npts - 1];
-        result = false;
+        result = TS_FALSE;
 
-        for (ia = 0; ia < polya->npts && !result; ia++)
+        for (ia = 0; ia < polya->npts && result != TS_TRUE; ia++)
         {
             /* Second point of polya's edge is a current one */
             sa.p[1] = polya->p[ia];
@@ -4097,8 +4204,12 @@ poly_overlap(PG_FUNCTION_ARGS)
             sa.p[0] = sa.p[1];
         }
 
-        if (!result)
+        if (result == TS_NULL)
+            PG_RETURN_NULL();
+
+        if (result == TS_FALSE)
         {
+            /* in the case of NaN is handled ealier */
             result = (point_inside(polya->p, polyb->npts, polyb->p) ||
                       point_inside(polyb->p, polya->npts, polya->p));
         }
@@ -4133,6 +4244,12 @@ touched_lseg_inside_poly(Point *a, Point *b, LSEG *s, POLYGON *poly, int start)
     t.p[0] = *a;
     t.p[1] = *b;
 
+    /*
+     * assume no parameters have NaN, so the tsbool functions shouldn't return
+     * TS_NULL.
+     */
+    Assert(!isnan(a->x) && !isnan(a->y) && !isnan(b->x) && !isnan(b->y) &&
+           !isnan(poly->boundbox.high.x));
     if (point_eq_point(a, s->p))
     {
         if (lseg_contain_point(&t, s->p + 1))
@@ -4160,7 +4277,7 @@ touched_lseg_inside_poly(Point *a, Point *b, LSEG *s, POLYGON *poly, int start)
  * start is used for optimization - function checks
  * polygon's edges starting from start
  */
-static bool
+static tsbool
 lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start)
 {
     LSEG        s,
@@ -4168,14 +4285,21 @@ lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start)
     int            i;
     bool        res = true,
                 intersection = false;
+    tsbool        res1;
+    tsbool        res2;
 
     t.p[0] = *a;
     t.p[1] = *b;
     s.p[0] = poly->p[(start == 0) ? (poly->npts - 1) : (start - 1)];
 
     /* Fast path. Check against boundbox. Also checks NaNs. */
-    if (!box_contain_point(&poly->boundbox, a) ||
-        !box_contain_point(&poly->boundbox, b))
+    res1 = box_contain_point(&poly->boundbox, a);
+    res2 = box_contain_point(&poly->boundbox, b);
+
+    if (res1 == TS_NULL || res2 == TS_NULL)
+        return TS_NULL;
+
+    if (!res1 || !res2)
         return false;
 
     for (i = start; i < poly->npts && res; i++)
@@ -4206,6 +4330,8 @@ lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start)
              */
 
             intersection = true;
+
+            /* the calls below won't return TS_NULL */
             res = lseg_inside_poly(t.p, &interpt, poly, i + 1);
             if (res)
                 res = lseg_inside_poly(t.p + 1, &interpt, poly, i + 1);
@@ -4234,11 +4360,12 @@ lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start)
 /*
  * Check whether the first polygon contains the second
  */
-static bool
+static tsbool
 poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly)
 {
     int            i;
     LSEG        s;
+    tsbool        r;
 
     Assert(contains_poly->npts > 0 && contained_poly->npts > 0);
 
@@ -4246,20 +4373,22 @@ poly_contain_poly(POLYGON *contains_poly, POLYGON *contained_poly)
      * Quick check to see if contained's bounding box is contained in
      * contains' bb.
      */
-    if (!box_contain_box(&contains_poly->boundbox, &contained_poly->boundbox))
-        return false;
+    r = box_contain_box(&contains_poly->boundbox, &contained_poly->boundbox);
+    if (r != TS_TRUE)
+        return r;
 
     s.p[0] = contained_poly->p[contained_poly->npts - 1];
 
     for (i = 0; i < contained_poly->npts; i++)
     {
         s.p[1] = contained_poly->p[i];
-        if (!lseg_inside_poly(s.p, s.p + 1, contains_poly, 0))
-            return false;
+        r = lseg_inside_poly(s.p, s.p + 1, contains_poly, 0);
+        if (r != TS_TRUE)
+            return r;
         s.p[0] = s.p[1];
     }
 
-    return true;
+    return TS_TRUE;
 }
 
 Datum
@@ -4277,7 +4406,7 @@ poly_contain(PG_FUNCTION_ARGS)
     PG_FREE_IF_COPY(polya, 0);
     PG_FREE_IF_COPY(polyb, 1);
 
-    PG_RETURN_BOOL(result);
+    PG_RETURN_TSBOOL(result);
 }
 
 
@@ -4300,7 +4429,7 @@ poly_contained(PG_FUNCTION_ARGS)
     PG_FREE_IF_COPY(polya, 0);
     PG_FREE_IF_COPY(polyb, 1);
 
-    PG_RETURN_BOOL(result);
+    PG_RETURN_TSBOOL(result);
 }
 
 
@@ -4310,7 +4439,7 @@ poly_contain_pt(PG_FUNCTION_ARGS)
     POLYGON    *poly = PG_GETARG_POLYGON_P(0);
     Point       *p = PG_GETARG_POINT_P(1);
 
-    PG_RETURN_BOOL(point_inside(p, poly->npts, poly->p) != 0);
+    PG_RETURN_TSBOOL(point_inside(p, poly->npts, poly->p) != 0);
 }
 
 Datum
@@ -4319,7 +4448,7 @@ pt_contained_poly(PG_FUNCTION_ARGS)
     Point       *p = PG_GETARG_POINT_P(0);
     POLYGON    *poly = PG_GETARG_POLYGON_P(1);
 
-    PG_RETURN_BOOL(point_inside(p, poly->npts, poly->p) != 0);
+    PG_RETURN_TSBOOL(point_inside(p, poly->npts, poly->p) != 0);
 }
 
 
@@ -5015,9 +5144,9 @@ circle_same(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(((isnan(circle1->radius) && isnan(circle1->radius)) ||
-                    FPeq(circle1->radius, circle2->radius)) &&
-                   point_eq_point(&circle1->center, &circle2->center));
+    PG_RETURN_TSBOOL(
+        TS_AND2(FPTeq(circle1->radius, circle2->radius),
+                point_eq_point(&circle1->center, &circle2->center)));
 }
 
 /*        circle_overlap    -        does circle1 overlap circle2?
@@ -5028,8 +5157,8 @@ circle_overlap(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
-                        float8_pl(circle1->radius, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTle(point_dt(&circle1->center, &circle2->center),
+                           float8_pl(circle1->radius, circle2->radius)));
 }
 
 /*        circle_overleft -        is the right edge of circle1 at or left of
@@ -5041,8 +5170,8 @@ circle_overleft(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPle(float8_pl(circle1->center.x, circle1->radius),
-                        float8_pl(circle2->center.x, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTle(float8_pl(circle1->center.x, circle1->radius),
+                           float8_pl(circle2->center.x, circle2->radius)));
 }
 
 /*        circle_left        -        is circle1 strictly left of circle2?
@@ -5053,8 +5182,8 @@ circle_left(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPlt(float8_pl(circle1->center.x, circle1->radius),
-                        float8_mi(circle2->center.x, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTlt(float8_pl(circle1->center.x, circle1->radius),
+                           float8_mi(circle2->center.x, circle2->radius)));
 }
 
 /*        circle_right    -        is circle1 strictly right of circle2?
@@ -5065,8 +5194,8 @@ circle_right(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPgt(float8_mi(circle1->center.x, circle1->radius),
-                        float8_pl(circle2->center.x, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTgt(float8_mi(circle1->center.x, circle1->radius),
+                           float8_pl(circle2->center.x, circle2->radius)));
 }
 
 /*        circle_overright    -    is the left edge of circle1 at or right of
@@ -5078,8 +5207,8 @@ circle_overright(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPge(float8_mi(circle1->center.x, circle1->radius),
-                        float8_mi(circle2->center.x, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTge(float8_mi(circle1->center.x, circle1->radius),
+                           float8_mi(circle2->center.x, circle2->radius)));
 }
 
 /*        circle_contained        -        is circle1 contained by circle2?
@@ -5102,8 +5231,8 @@ circle_contain(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPle(point_dt(&circle1->center, &circle2->center),
-                        float8_mi(circle1->radius, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTle(point_dt(&circle1->center, &circle2->center),
+                           float8_mi(circle1->radius, circle2->radius)));
 }
 
 
@@ -5115,8 +5244,8 @@ circle_below(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPlt(float8_pl(circle1->center.y, circle1->radius),
-                        float8_mi(circle2->center.y, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTlt(float8_pl(circle1->center.y, circle1->radius),
+                           float8_mi(circle2->center.y, circle2->radius)));
 }
 
 /*        circle_above    -        is circle1 strictly above circle2?
@@ -5127,8 +5256,8 @@ circle_above(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPgt(float8_mi(circle1->center.y, circle1->radius),
-                        float8_pl(circle2->center.y, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTgt(float8_mi(circle1->center.y, circle1->radius),
+                           float8_pl(circle2->center.y, circle2->radius)));
 }
 
 /*        circle_overbelow -        is the upper edge of circle1 at or below
@@ -5140,8 +5269,8 @@ circle_overbelow(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPle(float8_pl(circle1->center.y, circle1->radius),
-                        float8_pl(circle2->center.y, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTle(float8_pl(circle1->center.y, circle1->radius),
+                           float8_pl(circle2->center.y, circle2->radius)));
 }
 
 /*        circle_overabove    -    is the lower edge of circle1 at or above
@@ -5153,13 +5282,15 @@ circle_overabove(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPge(float8_mi(circle1->center.y, circle1->radius),
-                        float8_mi(circle2->center.y, circle2->radius)));
+    PG_RETURN_TSBOOL(FPTge(float8_mi(circle1->center.y, circle1->radius),
+                           float8_mi(circle2->center.y, circle2->radius)));
 }
 
 
 /*        circle_relop    -        is area(circle1) relop area(circle2), within
  *                                our accuracy constraint?
+ *
+ *  XXX; area comparison doen't consider the NaN-ness of the center location.
  */
 Datum
 circle_eq(PG_FUNCTION_ARGS)
@@ -5167,7 +5298,7 @@ circle_eq(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPeq(circle_ar(circle1), circle_ar(circle2)));
+    PG_RETURN_TSBOOL(FPTeq(circle_ar(circle1), circle_ar(circle2)));
 }
 
 Datum
@@ -5176,7 +5307,7 @@ circle_ne(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPne(circle_ar(circle1), circle_ar(circle2)));
+    PG_RETURN_TSBOOL(FPTne(circle_ar(circle1), circle_ar(circle2)));
 }
 
 Datum
@@ -5185,7 +5316,7 @@ circle_lt(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPlt(circle_ar(circle1), circle_ar(circle2)));
+    PG_RETURN_TSBOOL(FPTlt(circle_ar(circle1), circle_ar(circle2)));
 }
 
 Datum
@@ -5194,7 +5325,7 @@ circle_gt(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPgt(circle_ar(circle1), circle_ar(circle2)));
+    PG_RETURN_TSBOOL(FPTgt(circle_ar(circle1), circle_ar(circle2)));
 }
 
 Datum
@@ -5203,7 +5334,7 @@ circle_le(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPle(circle_ar(circle1), circle_ar(circle2)));
+    PG_RETURN_TSBOOL(FPTle(circle_ar(circle1), circle_ar(circle2)));
 }
 
 Datum
@@ -5212,7 +5343,7 @@ circle_ge(PG_FUNCTION_ARGS)
     CIRCLE       *circle1 = PG_GETARG_CIRCLE_P(0);
     CIRCLE       *circle2 = PG_GETARG_CIRCLE_P(1);
 
-    PG_RETURN_BOOL(FPge(circle_ar(circle1), circle_ar(circle2)));
+    PG_RETURN_TSBOOL(FPTge(circle_ar(circle1), circle_ar(circle2)));
 }
 
 
@@ -5348,6 +5479,11 @@ circle_contain_pt(PG_FUNCTION_ARGS)
     float8        d;
 
     d = point_dt(&circle->center, point);
+
+    if (isnan(d) || isnan(circle->radius))
+        PG_RETURN_NULL();
+
+    /*  XXX: why don't we use FP(T)le? */
     PG_RETURN_BOOL(d <= circle->radius);
 }
 
@@ -5360,6 +5496,10 @@ pt_contained_circle(PG_FUNCTION_ARGS)
     float8        d;
 
     d = point_dt(&circle->center, point);
+    if (isnan(d) || isnan(circle->radius))
+        PG_RETURN_NULL();
+
+    /*  XXX: why don't we use FP(T)le? */
     PG_RETURN_BOOL(d <= circle->radius);
 }
 
@@ -5586,8 +5726,9 @@ poly_circle(PG_FUNCTION_ARGS)
  ***********************************************************************/
 
 /*
- *    Test to see if the point is inside the polygon, returns 1/0, or 2 if
- *    the point is on the polygon.
+ *    Test to see if the point is inside the polygon, returns 1/0, or 2 if the
+ *    point is on the polygon. -1 means undetermined, in case any operand is an
+ *    invalid object. (-1, 0 and 1 are compatible with tsbool type)
  *    Code adapted but not copied from integer-based routines in WN: A
  *    Server for the HTTP
  *    version 1.15.1, file wn/image.c
@@ -5619,7 +5760,7 @@ point_inside(Point *p, int npts, Point *plist)
 
     /* NaN makes the point cannot be inside the polygon */
     if (unlikely(isnan(x0) || isnan(y0) || isnan(p->x) || isnan(p->y)))
-        return 0;
+        return -1;
 
     prev_x = x0;
     prev_y = y0;
@@ -5632,7 +5773,7 @@ point_inside(Point *p, int npts, Point *plist)
 
         /* NaN makes the point cannot be inside the polygon */
         if (unlikely(isnan(x) || isnan(y)))
-            return 0;
+            return -1;
 
         /* compute previous to current point crossing */
         if ((cross = lseg_crossing(x, y, prev_x, prev_y)) == POINT_ON_POLYGON)
@@ -5659,6 +5800,8 @@ point_inside(Point *p, int npts, Point *plist)
  * Returns +/-1 if one point is on the positive X-axis.
  * Returns 0 if both points are on the positive X-axis, or there is no crossing.
  * Returns POINT_ON_POLYGON if the segment contains (0,0).
+ * This function doesn't check if the parameters contain NaNs, it's the
+ * responsibility of the callers.
  * Wow, that is one confusing API, but it is used above, and when summed,
  * can tell is if a point is in a polygon.
  */
@@ -5723,17 +5866,18 @@ lseg_crossing(float8 x, float8 y, float8 prev_x, float8 prev_y)
 }
 
 
-static bool
+static tsbool
 plist_same(int npts, Point *p1, Point *p2)
 {
     int            i,
                 ii,
                 j;
+    tsbool        r;
 
     /* find match for first point */
     for (i = 0; i < npts; i++)
     {
-        if (point_eq_point(&p2[i], &p1[0]))
+        if ((r = point_eq_point(&p2[i], &p1[0])) == TS_TRUE)
         {
 
             /* match found? then look forward through remaining points */
@@ -5741,26 +5885,29 @@ plist_same(int npts, Point *p1, Point *p2)
             {
                 if (j >= npts)
                     j = 0;
-                if (!point_eq_point(&p2[j], &p1[ii]))
+                if ((r = point_eq_point(&p2[j], &p1[ii])) != TS_TRUE)
                     break;
             }
             if (ii == npts)
-                return true;
+                return TS_TRUE;
 
             /* match not found forwards? then look backwards */
             for (ii = 1, j = i - 1; ii < npts; ii++, j--)
             {
                 if (j < 0)
                     j = (npts - 1);
-                if (!point_eq_point(&p2[j], &p1[ii]))
+                if ((r = point_eq_point(&p2[j], &p1[ii])) != TS_TRUE)
                     break;
             }
             if (ii == npts)
-                return true;
+                return TS_TRUE;
         }
+
+        if (r == TS_NULL)
+            return TS_NULL;
     }
 
-    return false;
+    return TS_FALSE;
 }
 
 
diff --git a/src/include/c.h b/src/include/c.h
index c8ede08273..03e541936e 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -402,6 +402,13 @@ typedef unsigned char bool;
 #endif                            /* not PG_USE_STDBOOL */
 #endif                            /* not C++ */
 
+/* tri-state boolean, false/true are compatible with bool */
+typedef enum tsbool
+{
+    TS_NULL = -1,
+    TS_FALSE = false,
+    TS_TRUE = true
+} tsbool;
 
 /* ----------------------------------------------------------------
  *                Section 3:    standard system types
diff --git a/src/include/utils/float.h b/src/include/utils/float.h
index 4ab3f9d8ef..a983d6d8d0 100644
--- a/src/include/utils/float.h
+++ b/src/include/utils/float.h
@@ -353,6 +353,115 @@ float8_max(const float8 val1, const float8 val2)
     return float8_gt(val1, val2) ? val1 : val2;
 }
 
+/* tri-state equivalents */
+static inline tsbool
+float4_teq(const float4 val1, const float4 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 == val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float8_teq(const float8 val1, const float8 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 == val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float4_tne(const float4 val1, const float4 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 != val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float8_tne(const float8 val1, const float8 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 != val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float4_tlt(const float4 val1, const float4 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 < val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float8_tlt(const float8 val1, const float8 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 < val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float4_tle(const float4 val1, const float4 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 <= val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float8_tle(const float8 val1, const float8 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 <= val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float4_tgt(const float4 val1, const float4 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 > val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float8_tgt(const float8 val1, const float8 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 > val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float4_tge(const float4 val1, const float4 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 >= val2;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+float8_tge(const float8 val1, const float8 val2)
+{
+    if (!isnan(val1) && !isnan(val2))
+        return val1 >= val2;
+
+    return TS_NULL;
+}
+
 /*
  * These two functions return NaN if either input is NaN, else the smaller
  * of the two inputs.  This does NOT follow our usual sort rule, but it is
diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h
index 0b87437d83..7b8a0dbdf7 100644
--- a/src/include/utils/geo_decls.h
+++ b/src/include/utils/geo_decls.h
@@ -40,6 +40,19 @@
 
 #define EPSILON                    1.0E-06
 
+/* helper function for tri-state checking */
+static inline tsbool
+FP_TRICHECK(double A, double B, bool cond)
+{
+    if (cond)
+        return TS_TRUE;
+    if (!isnan(A) && !isnan(B))
+        return TS_FALSE;
+
+    return TS_NULL;
+}
+
+
 #ifdef EPSILON
 #define FPzero(A)                (fabs(A) <= EPSILON)
 
@@ -78,6 +91,53 @@ FPge(double A, double B)
 {
     return A + EPSILON >= B;
 }
+
+/* tri-state comparisons, don't use define to avoid duplicate evaluation */
+static inline tsbool
+FPTzero(double A)
+{
+    if (fabs(A) <= EPSILON)
+        return TS_TRUE;
+    if (isnan(A))
+        return TS_NULL;
+    return TS_FALSE;
+}
+
+static inline tsbool
+FPTeq(double A, double B)
+{
+    return FP_TRICHECK(A, B, A == B || fabs(A - B) <= EPSILON);
+}
+
+static inline tsbool
+FPTne(double A, double B)
+{
+    return FP_TRICHECK(A, B, A != B && fabs(A - B) > EPSILON);
+}
+
+static inline tsbool
+FPTlt(double A, double B)
+{
+    return FP_TRICHECK(A, B, A + EPSILON < B);
+}
+
+static inline tsbool
+FPTle(double A, double B)
+{
+    return FP_TRICHECK(A, B, A <= B + EPSILON);
+}
+
+static inline tsbool
+FPTgt(double A, double B)
+{
+    return FP_TRICHECK(A, B, A > B + EPSILON);
+}
+
+static inline tsbool
+FPTge(double A, double B)
+{
+    return FP_TRICHECK(A, B, A + EPSILON >= B);
+}
 #else
 #define FPzero(A)                ((A) == 0)
 #define FPeq(A,B)                ((A) == (B))
@@ -86,10 +146,109 @@ FPge(double A, double B)
 #define FPle(A,B)                ((A) <= (B))
 #define FPgt(A,B)                ((A) > (B))
 #define FPge(A,B)                ((A) >= (B))
+
+/* define as inline functions to avoid duplicate evaluation */
+static inline tsbool
+FPTzero(double A)
+{
+    if (fabs(A) <= EPSILON)
+        return TS_TRUE;
+    if (isnan(A))
+        return TS_NULL;
+    return TS_FALSE;
+}
+
+static inline tsbool
+FPTeq(double A, double B)
+{
+    return FP_TRICHECK(A, B, A == B);
+}
+
+static inline tsbool
+FPTne(double A, double B)
+{
+    return FP_TRICHECK(A, B, A != B);
+}
+
+static inline tsbool
+FPTlt(double A, double B)
+{
+    return FP_TRICHECK(A, B, A < B);
+}
+
+static inline tsbool
+FPTle(double A, double B)
+{
+    return FP_TRICHECK(A, B, A <= B);
+}
+
+static inline tsbool
+FPTgt(double A, double B)
+{
+    return FP_TRICHECK(A, B, A > B);
+}
+
+static inline tsbool
+FPge(double A, double B)
+{
+    return FP_TRICHECK(A, B, A >= B);
+}
 #endif
 
 #define HYPOT(A, B)                pg_hypot(A, B)
 
+static inline tsbool
+TS_NOT(tsbool a)
+{
+    if (a != TS_NULL)
+        return !a;
+
+    return TS_NULL;
+}
+
+static inline tsbool
+TS_OR2(tsbool p1, tsbool p2)
+{
+    if (p1 == TS_TRUE || p2 == TS_TRUE)
+        return TS_TRUE;
+    if (p1 == TS_NULL || p2 == TS_NULL)
+        return TS_NULL;
+    else
+        return TS_FALSE;
+}
+
+static inline tsbool
+TS_AND2(tsbool p1, tsbool p2)
+{
+    if (p1 == TS_TRUE && p2 == TS_TRUE)
+        return TS_TRUE;
+    if (p1 == TS_NULL || p2 == TS_NULL)
+        return TS_NULL;
+    else
+        return TS_FALSE;
+}
+
+static inline tsbool
+TS_AND4(tsbool p1, tsbool p2, tsbool p3, tsbool p4)
+{
+    if (p1 == TS_TRUE && p2 == TS_TRUE && p3 == TS_TRUE && p4 == TS_TRUE)
+        return TS_TRUE;
+    if (p1 == TS_NULL || p2 == TS_NULL || p3 == TS_NULL || p4 ==TS_NULL)
+        return TS_NULL;
+    else
+        return TS_FALSE;
+}
+
+#define PG_RETURN_TSBOOL(e)            \
+    do                                \
+    {                                \
+        tsbool _tmpsb = (e);        \
+        if (_tmpsb != TS_NULL)        \
+            PG_RETURN_BOOL(_tmpsb);    \
+        else                        \
+            PG_RETURN_NULL();        \
+    } while (0)
+
 /*---------------------------------------------------------------------
  * Point - (x,y)
  *-------------------------------------------------------------------*/

Re: Issue with point_ops and NaN

From
Laurenz Albe
Date:
On Thu, 2021-04-01 at 09:35 +0900, Kyotaro Horiguchi wrote:
> > > > > > > > SELECT point('NaN','NaN') <@ polygon('(0,0),(1,0),(1,1),(0,0)');
> > > > > > > > ?column? 
> > > > > > > > ----------
> > > > > > > >    t
> > > > > > > >    (1 row)
> > 
> > If you think of "NaN" literally as "not a number", then FALSE would
> > make sense, since "not a number" implies "not a number between 0 and 1".
> > But since NaN is the result of operations like 0/0 or infinity - infinity,
> > NULL might be better.
> > So I'd opt for NULL too.
> 
> Thanks.  Do you think it's acceptable that returning false instead of
> NULL as an alternative behavior?

Yes, I think that is acceptable.

Yours,
Laurenz Albe