From db04f2b3225aa7e6d496a087c4c0e46161744573 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 3 Aug 2010 21:21:03 +0000 Subject: [PATCH] Replace the naive HYPOT() macro with a standards-conformant hypotenuse function. This avoids unnecessary overflows and probably gives a more accurate result as well. Paul Matthews, reviewed by Andrew Geery --- src/backend/utils/adt/geo_ops.c | 62 ++++++++++++++++++++++++++++++++++++++++- src/include/utils/geo_decls.h | 5 ++-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c index cd1d6c2cc6..3eef2f47da 100644 --- a/src/backend/utils/adt/geo_ops.c +++ b/src/backend/utils/adt/geo_ops.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/geo_ops.c,v 1.108 2010/02/26 02:01:08 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/geo_ops.c,v 1.109 2010/08/03 21:21:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -5410,3 +5410,63 @@ plist_same(int npts, Point *p1, Point *p2) return FALSE; } + + +/*------------------------------------------------------------------------- + * Determine the hypotenuse. + * + * If required, x and y are swapped to make x the larger number. The + * traditional formula of x^2+y^2 is rearranged to factor x outside the + * sqrt. This allows computation of the hypotenuse for significantly + * larger values, and with a higher precision than when using the naive + * formula. In particular, this cannot overflow unless the final result + * would be out-of-range. + * + * sqrt( x^2 + y^2 ) = sqrt( x^2( 1 + y^2/x^2) ) + * = x * sqrt( 1 + y^2/x^2 ) + * = x * sqrt( 1 + y/x * y/x ) + * + * It is expected that this routine will eventually be replaced with the + * C99 hypot() function. + * + * This implementation conforms to IEEE Std 1003.1 and GLIBC, in that the + * case of hypot(inf,nan) results in INF, and not NAN. + *----------------------------------------------------------------------- + */ +double +pg_hypot(double x, double y) +{ + double yx; + + /* Handle INF and NaN properly */ + if (isinf(x) || isinf(y)) + return get_float8_infinity(); + + if (isnan(x) || isnan(y)) + return get_float8_nan(); + + /* Else, drop any minus signs */ + x = fabs(x); + y = fabs(y); + + /* Swap x and y if needed to make x the larger one */ + if (x < y) + { + double temp = x; + + x = y; + y = temp; + } + + /* + * If y is zero, the hypotenuse is x. This test saves a few cycles in + * such cases, but more importantly it also protects against + * divide-by-zero errors, since now x >= y. + */ + if (y == 0.0) + return x; + + /* Determine the hypotenuse */ + yx = y / x; + return x * sqrt(1.0 + (yx * yx)); +} diff --git a/src/include/utils/geo_decls.h b/src/include/utils/geo_decls.h index f43d2d5fdc..904d9f7948 100644 --- a/src/include/utils/geo_decls.h +++ b/src/include/utils/geo_decls.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/geo_decls.h,v 1.57 2010/01/14 16:31:09 teodor Exp $ + * $PostgreSQL: pgsql/src/include/utils/geo_decls.h,v 1.58 2010/08/03 21:21:03 tgl Exp $ * * NOTE * These routines do *not* use the float types from adt/. @@ -50,7 +50,7 @@ #define FPge(A,B) ((A) >= (B)) #endif -#define HYPOT(A, B) sqrt((A) * (A) + (B) * (B)) +#define HYPOT(A, B) pg_hypot(A, B) /*--------------------------------------------------------------------- * Point - (x,y) @@ -211,6 +211,7 @@ extern Datum point_div(PG_FUNCTION_ARGS); /* private routines */ extern double point_dt(Point *pt1, Point *pt2); extern double point_sl(Point *pt1, Point *pt2); +extern double pg_hypot(double x, double y); /* public lseg routines */ extern Datum lseg_in(PG_FUNCTION_ARGS); -- 2.11.0