mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
When there is just one non-null input value, and it is infinity or NaN, aggregates such as stddev_pop and covar_pop should produce a NaN result, because the calculation is not well-defined. They used to do so, but since we adopted Youngs-Cramer aggregation in commit e954a727f, they produced zero instead. That's an oversight, so fix it. Add tests exercising these edge cases. Affected aggregates are var_pop(double precision) stddev_pop(double precision) var_pop(real) stddev_pop(real) regr_sxx(double precision,double precision) regr_syy(double precision,double precision) regr_sxy(double precision,double precision) regr_r2(double precision,double precision) regr_slope(double precision,double precision) regr_intercept(double precision,double precision) covar_pop(double precision,double precision) corr(double precision,double precision) Back-patch to v12 where the behavior change was accidentally introduced. Report and patch by me; thanks to Dean Rasheed for review. Discussion: https://postgr.es/m/353062.1591898766@sss.pgh.pa.us
3989 lines
90 KiB
C
3989 lines
90 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* float.c
|
|
* Functions for the built-in floating-point types.
|
|
*
|
|
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/float.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <ctype.h>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include <limits.h>
|
|
|
|
#include "catalog/pg_type.h"
|
|
#include "common/int.h"
|
|
#include "common/shortest_dec.h"
|
|
#include "libpq/pqformat.h"
|
|
#include "miscadmin.h"
|
|
#include "utils/array.h"
|
|
#include "utils/float.h"
|
|
#include "utils/fmgrprotos.h"
|
|
#include "utils/sortsupport.h"
|
|
#include "utils/timestamp.h"
|
|
|
|
|
|
/*
|
|
* Configurable GUC parameter
|
|
*
|
|
* If >0, use shortest-decimal format for output; this is both the default and
|
|
* allows for compatibility with clients that explicitly set a value here to
|
|
* get round-trip-accurate results. If 0 or less, then use the old, slow,
|
|
* decimal rounding method.
|
|
*/
|
|
int extra_float_digits = 1;
|
|
|
|
/* Cached constants for degree-based trig functions */
|
|
static bool degree_consts_set = false;
|
|
static float8 sin_30 = 0;
|
|
static float8 one_minus_cos_60 = 0;
|
|
static float8 asin_0_5 = 0;
|
|
static float8 acos_0_5 = 0;
|
|
static float8 atan_1_0 = 0;
|
|
static float8 tan_45 = 0;
|
|
static float8 cot_45 = 0;
|
|
|
|
/*
|
|
* These are intentionally not static; don't "fix" them. They will never
|
|
* be referenced by other files, much less changed; but we don't want the
|
|
* compiler to know that, else it might try to precompute expressions
|
|
* involving them. See comments for init_degree_constants().
|
|
*/
|
|
float8 degree_c_thirty = 30.0;
|
|
float8 degree_c_forty_five = 45.0;
|
|
float8 degree_c_sixty = 60.0;
|
|
float8 degree_c_one_half = 0.5;
|
|
float8 degree_c_one = 1.0;
|
|
|
|
/* State for drandom() and setseed() */
|
|
static bool drandom_seed_set = false;
|
|
static unsigned short drandom_seed[3] = {0, 0, 0};
|
|
|
|
/* Local function prototypes */
|
|
static double sind_q1(double x);
|
|
static double cosd_q1(double x);
|
|
static void init_degree_constants(void);
|
|
|
|
|
|
/*
|
|
* We use these out-of-line ereport() calls to report float overflow,
|
|
* underflow, and zero-divide, because following our usual practice of
|
|
* repeating them at each call site would lead to a lot of code bloat.
|
|
*
|
|
* This does mean that you don't get a useful error location indicator.
|
|
*/
|
|
pg_noinline void
|
|
float_overflow_error(void)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("value out of range: overflow")));
|
|
}
|
|
|
|
pg_noinline void
|
|
float_underflow_error(void)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("value out of range: underflow")));
|
|
}
|
|
|
|
pg_noinline void
|
|
float_zero_divide_error(void)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DIVISION_BY_ZERO),
|
|
errmsg("division by zero")));
|
|
}
|
|
|
|
|
|
/*
|
|
* Returns -1 if 'val' represents negative infinity, 1 if 'val'
|
|
* represents (positive) infinity, and 0 otherwise. On some platforms,
|
|
* this is equivalent to the isinf() macro, but not everywhere: C99
|
|
* does not specify that isinf() needs to distinguish between positive
|
|
* and negative infinity.
|
|
*/
|
|
int
|
|
is_infinite(double val)
|
|
{
|
|
int inf = isinf(val);
|
|
|
|
if (inf == 0)
|
|
return 0;
|
|
else if (val > 0)
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* ========== USER I/O ROUTINES ========== */
|
|
|
|
|
|
/*
|
|
* float4in - converts "num" to float4
|
|
*
|
|
* Note that this code now uses strtof(), where it used to use strtod().
|
|
*
|
|
* The motivation for using strtof() is to avoid a double-rounding problem:
|
|
* for certain decimal inputs, if you round the input correctly to a double,
|
|
* and then round the double to a float, the result is incorrect in that it
|
|
* does not match the result of rounding the decimal value to float directly.
|
|
*
|
|
* One of the best examples is 7.038531e-26:
|
|
*
|
|
* 0xAE43FDp-107 = 7.03853069185120912085...e-26
|
|
* midpoint 7.03853100000000022281...e-26
|
|
* 0xAE43FEp-107 = 7.03853130814879132477...e-26
|
|
*
|
|
* making 0xAE43FDp-107 the correct float result, but if you do the conversion
|
|
* via a double, you get
|
|
*
|
|
* 0xAE43FD.7FFFFFF8p-107 = 7.03853099999999907487...e-26
|
|
* midpoint 7.03853099999999964884...e-26
|
|
* 0xAE43FD.80000000p-107 = 7.03853100000000022281...e-26
|
|
* 0xAE43FD.80000008p-107 = 7.03853100000000137076...e-26
|
|
*
|
|
* so the value rounds to the double exactly on the midpoint between the two
|
|
* nearest floats, and then rounding again to a float gives the incorrect
|
|
* result of 0xAE43FEp-107.
|
|
*
|
|
*/
|
|
Datum
|
|
float4in(PG_FUNCTION_ARGS)
|
|
{
|
|
char *num = PG_GETARG_CSTRING(0);
|
|
char *orig_num;
|
|
float val;
|
|
char *endptr;
|
|
|
|
/*
|
|
* endptr points to the first character _after_ the sequence we recognized
|
|
* as a valid floating point number. orig_num points to the original input
|
|
* string.
|
|
*/
|
|
orig_num = num;
|
|
|
|
/* skip leading whitespace */
|
|
while (*num != '\0' && isspace((unsigned char) *num))
|
|
num++;
|
|
|
|
/*
|
|
* Check for an empty-string input to begin with, to avoid the vagaries of
|
|
* strtod() on different platforms.
|
|
*/
|
|
if (*num == '\0')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
"real", orig_num)));
|
|
|
|
errno = 0;
|
|
val = strtof(num, &endptr);
|
|
|
|
/* did we not see anything that looks like a double? */
|
|
if (endptr == num || errno != 0)
|
|
{
|
|
int save_errno = errno;
|
|
|
|
/*
|
|
* C99 requires that strtof() accept NaN, [+-]Infinity, and [+-]Inf,
|
|
* but not all platforms support all of these (and some accept them
|
|
* but set ERANGE anyway...) Therefore, we check for these inputs
|
|
* ourselves if strtof() fails.
|
|
*
|
|
* Note: C99 also requires hexadecimal input as well as some extended
|
|
* forms of NaN, but we consider these forms unportable and don't try
|
|
* to support them. You can use 'em if your strtof() takes 'em.
|
|
*/
|
|
if (pg_strncasecmp(num, "NaN", 3) == 0)
|
|
{
|
|
val = get_float4_nan();
|
|
endptr = num + 3;
|
|
}
|
|
else if (pg_strncasecmp(num, "Infinity", 8) == 0)
|
|
{
|
|
val = get_float4_infinity();
|
|
endptr = num + 8;
|
|
}
|
|
else if (pg_strncasecmp(num, "+Infinity", 9) == 0)
|
|
{
|
|
val = get_float4_infinity();
|
|
endptr = num + 9;
|
|
}
|
|
else if (pg_strncasecmp(num, "-Infinity", 9) == 0)
|
|
{
|
|
val = -get_float4_infinity();
|
|
endptr = num + 9;
|
|
}
|
|
else if (pg_strncasecmp(num, "inf", 3) == 0)
|
|
{
|
|
val = get_float4_infinity();
|
|
endptr = num + 3;
|
|
}
|
|
else if (pg_strncasecmp(num, "+inf", 4) == 0)
|
|
{
|
|
val = get_float4_infinity();
|
|
endptr = num + 4;
|
|
}
|
|
else if (pg_strncasecmp(num, "-inf", 4) == 0)
|
|
{
|
|
val = -get_float4_infinity();
|
|
endptr = num + 4;
|
|
}
|
|
else if (save_errno == ERANGE)
|
|
{
|
|
/*
|
|
* Some platforms return ERANGE for denormalized numbers (those
|
|
* that are not zero, but are too close to zero to have full
|
|
* precision). We'd prefer not to throw error for that, so try to
|
|
* detect whether it's a "real" out-of-range condition by checking
|
|
* to see if the result is zero or huge.
|
|
*
|
|
* Use isinf() rather than HUGE_VALF on VS2013 because it
|
|
* generates a spurious overflow warning for -HUGE_VALF. Also use
|
|
* isinf() if HUGE_VALF is missing.
|
|
*/
|
|
if (val == 0.0 ||
|
|
#if !defined(HUGE_VALF) || (defined(_MSC_VER) && (_MSC_VER < 1900))
|
|
isinf(val)
|
|
#else
|
|
(val >= HUGE_VALF || val <= -HUGE_VALF)
|
|
#endif
|
|
)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("\"%s\" is out of range for type real",
|
|
orig_num)));
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
"real", orig_num)));
|
|
}
|
|
#ifdef HAVE_BUGGY_SOLARIS_STRTOD
|
|
else
|
|
{
|
|
/*
|
|
* Many versions of Solaris have a bug wherein strtod sets endptr to
|
|
* point one byte beyond the end of the string when given "inf" or
|
|
* "infinity".
|
|
*/
|
|
if (endptr != num && endptr[-1] == '\0')
|
|
endptr--;
|
|
}
|
|
#endif /* HAVE_BUGGY_SOLARIS_STRTOD */
|
|
|
|
/* skip trailing whitespace */
|
|
while (*endptr != '\0' && isspace((unsigned char) *endptr))
|
|
endptr++;
|
|
|
|
/* if there is any junk left at the end of the string, bail out */
|
|
if (*endptr != '\0')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
"real", orig_num)));
|
|
|
|
PG_RETURN_FLOAT4(val);
|
|
}
|
|
|
|
/*
|
|
* float4out - converts a float4 number to a string
|
|
* using a standard output format
|
|
*/
|
|
Datum
|
|
float4out(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 num = PG_GETARG_FLOAT4(0);
|
|
char *ascii = (char *) palloc(32);
|
|
int ndig = FLT_DIG + extra_float_digits;
|
|
|
|
if (extra_float_digits > 0)
|
|
{
|
|
float_to_shortest_decimal_buf(num, ascii);
|
|
PG_RETURN_CSTRING(ascii);
|
|
}
|
|
|
|
(void) pg_strfromd(ascii, 32, ndig, num);
|
|
PG_RETURN_CSTRING(ascii);
|
|
}
|
|
|
|
/*
|
|
* float4recv - converts external binary format to float4
|
|
*/
|
|
Datum
|
|
float4recv(PG_FUNCTION_ARGS)
|
|
{
|
|
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
|
|
|
PG_RETURN_FLOAT4(pq_getmsgfloat4(buf));
|
|
}
|
|
|
|
/*
|
|
* float4send - converts float4 to binary format
|
|
*/
|
|
Datum
|
|
float4send(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 num = PG_GETARG_FLOAT4(0);
|
|
StringInfoData buf;
|
|
|
|
pq_begintypsend(&buf);
|
|
pq_sendfloat4(&buf, num);
|
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
}
|
|
|
|
/*
|
|
* float8in - converts "num" to float8
|
|
*/
|
|
Datum
|
|
float8in(PG_FUNCTION_ARGS)
|
|
{
|
|
char *num = PG_GETARG_CSTRING(0);
|
|
|
|
PG_RETURN_FLOAT8(float8in_internal(num, NULL, "double precision", num));
|
|
}
|
|
|
|
/* Convenience macro: set *have_error flag (if provided) or throw error */
|
|
#define RETURN_ERROR(throw_error, have_error) \
|
|
do { \
|
|
if (have_error) { \
|
|
*have_error = true; \
|
|
return 0.0; \
|
|
} else { \
|
|
throw_error; \
|
|
} \
|
|
} while (0)
|
|
|
|
/*
|
|
* float8in_internal_opt_error - guts of float8in()
|
|
*
|
|
* This is exposed for use by functions that want a reasonably
|
|
* platform-independent way of inputting doubles. The behavior is
|
|
* essentially like strtod + ereport on error, but note the following
|
|
* differences:
|
|
* 1. Both leading and trailing whitespace are skipped.
|
|
* 2. If endptr_p is NULL, we throw error if there's trailing junk.
|
|
* Otherwise, it's up to the caller to complain about trailing junk.
|
|
* 3. In event of a syntax error, the report mentions the given type_name
|
|
* and prints orig_string as the input; this is meant to support use of
|
|
* this function with types such as "box" and "point", where what we are
|
|
* parsing here is just a substring of orig_string.
|
|
*
|
|
* "num" could validly be declared "const char *", but that results in an
|
|
* unreasonable amount of extra casting both here and in callers, so we don't.
|
|
*
|
|
* When "*have_error" flag is provided, it's set instead of throwing an
|
|
* error. This is helpful when caller need to handle errors by itself.
|
|
*/
|
|
double
|
|
float8in_internal_opt_error(char *num, char **endptr_p,
|
|
const char *type_name, const char *orig_string,
|
|
bool *have_error)
|
|
{
|
|
double val;
|
|
char *endptr;
|
|
|
|
if (have_error)
|
|
*have_error = false;
|
|
|
|
/* skip leading whitespace */
|
|
while (*num != '\0' && isspace((unsigned char) *num))
|
|
num++;
|
|
|
|
/*
|
|
* Check for an empty-string input to begin with, to avoid the vagaries of
|
|
* strtod() on different platforms.
|
|
*/
|
|
if (*num == '\0')
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type %s: \"%s\"",
|
|
type_name, orig_string))),
|
|
have_error);
|
|
|
|
errno = 0;
|
|
val = strtod(num, &endptr);
|
|
|
|
/* did we not see anything that looks like a double? */
|
|
if (endptr == num || errno != 0)
|
|
{
|
|
int save_errno = errno;
|
|
|
|
/*
|
|
* C99 requires that strtod() accept NaN, [+-]Infinity, and [+-]Inf,
|
|
* but not all platforms support all of these (and some accept them
|
|
* but set ERANGE anyway...) Therefore, we check for these inputs
|
|
* ourselves if strtod() fails.
|
|
*
|
|
* Note: C99 also requires hexadecimal input as well as some extended
|
|
* forms of NaN, but we consider these forms unportable and don't try
|
|
* to support them. You can use 'em if your strtod() takes 'em.
|
|
*/
|
|
if (pg_strncasecmp(num, "NaN", 3) == 0)
|
|
{
|
|
val = get_float8_nan();
|
|
endptr = num + 3;
|
|
}
|
|
else if (pg_strncasecmp(num, "Infinity", 8) == 0)
|
|
{
|
|
val = get_float8_infinity();
|
|
endptr = num + 8;
|
|
}
|
|
else if (pg_strncasecmp(num, "+Infinity", 9) == 0)
|
|
{
|
|
val = get_float8_infinity();
|
|
endptr = num + 9;
|
|
}
|
|
else if (pg_strncasecmp(num, "-Infinity", 9) == 0)
|
|
{
|
|
val = -get_float8_infinity();
|
|
endptr = num + 9;
|
|
}
|
|
else if (pg_strncasecmp(num, "inf", 3) == 0)
|
|
{
|
|
val = get_float8_infinity();
|
|
endptr = num + 3;
|
|
}
|
|
else if (pg_strncasecmp(num, "+inf", 4) == 0)
|
|
{
|
|
val = get_float8_infinity();
|
|
endptr = num + 4;
|
|
}
|
|
else if (pg_strncasecmp(num, "-inf", 4) == 0)
|
|
{
|
|
val = -get_float8_infinity();
|
|
endptr = num + 4;
|
|
}
|
|
else if (save_errno == ERANGE)
|
|
{
|
|
/*
|
|
* Some platforms return ERANGE for denormalized numbers (those
|
|
* that are not zero, but are too close to zero to have full
|
|
* precision). We'd prefer not to throw error for that, so try to
|
|
* detect whether it's a "real" out-of-range condition by checking
|
|
* to see if the result is zero or huge.
|
|
*
|
|
* On error, we intentionally complain about double precision not
|
|
* the given type name, and we print only the part of the string
|
|
* that is the current number.
|
|
*/
|
|
if (val == 0.0 || val >= HUGE_VAL || val <= -HUGE_VAL)
|
|
{
|
|
char *errnumber = pstrdup(num);
|
|
|
|
errnumber[endptr - num] = '\0';
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("\"%s\" is out of range for type double precision",
|
|
errnumber))),
|
|
have_error);
|
|
}
|
|
}
|
|
else
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type "
|
|
"%s: \"%s\"",
|
|
type_name, orig_string))),
|
|
have_error);
|
|
}
|
|
#ifdef HAVE_BUGGY_SOLARIS_STRTOD
|
|
else
|
|
{
|
|
/*
|
|
* Many versions of Solaris have a bug wherein strtod sets endptr to
|
|
* point one byte beyond the end of the string when given "inf" or
|
|
* "infinity".
|
|
*/
|
|
if (endptr != num && endptr[-1] == '\0')
|
|
endptr--;
|
|
}
|
|
#endif /* HAVE_BUGGY_SOLARIS_STRTOD */
|
|
|
|
/* skip trailing whitespace */
|
|
while (*endptr != '\0' && isspace((unsigned char) *endptr))
|
|
endptr++;
|
|
|
|
/* report stopping point if wanted, else complain if not end of string */
|
|
if (endptr_p)
|
|
*endptr_p = endptr;
|
|
else if (*endptr != '\0')
|
|
RETURN_ERROR(ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
|
|
errmsg("invalid input syntax for type "
|
|
"%s: \"%s\"",
|
|
type_name, orig_string))),
|
|
have_error);
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
* Interface to float8in_internal_opt_error() without "have_error" argument.
|
|
*/
|
|
double
|
|
float8in_internal(char *num, char **endptr_p,
|
|
const char *type_name, const char *orig_string)
|
|
{
|
|
return float8in_internal_opt_error(num, endptr_p, type_name,
|
|
orig_string, NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* float8out - converts float8 number to a string
|
|
* using a standard output format
|
|
*/
|
|
Datum
|
|
float8out(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 num = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_CSTRING(float8out_internal(num));
|
|
}
|
|
|
|
/*
|
|
* float8out_internal - guts of float8out()
|
|
*
|
|
* This is exposed for use by functions that want a reasonably
|
|
* platform-independent way of outputting doubles.
|
|
* The result is always palloc'd.
|
|
*/
|
|
char *
|
|
float8out_internal(double num)
|
|
{
|
|
char *ascii = (char *) palloc(32);
|
|
int ndig = DBL_DIG + extra_float_digits;
|
|
|
|
if (extra_float_digits > 0)
|
|
{
|
|
double_to_shortest_decimal_buf(num, ascii);
|
|
return ascii;
|
|
}
|
|
|
|
(void) pg_strfromd(ascii, 32, ndig, num);
|
|
return ascii;
|
|
}
|
|
|
|
/*
|
|
* float8recv - converts external binary format to float8
|
|
*/
|
|
Datum
|
|
float8recv(PG_FUNCTION_ARGS)
|
|
{
|
|
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
|
|
|
PG_RETURN_FLOAT8(pq_getmsgfloat8(buf));
|
|
}
|
|
|
|
/*
|
|
* float8send - converts float8 to binary format
|
|
*/
|
|
Datum
|
|
float8send(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 num = PG_GETARG_FLOAT8(0);
|
|
StringInfoData buf;
|
|
|
|
pq_begintypsend(&buf);
|
|
pq_sendfloat8(&buf, num);
|
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
}
|
|
|
|
|
|
/* ========== PUBLIC ROUTINES ========== */
|
|
|
|
|
|
/*
|
|
* ======================
|
|
* FLOAT4 BASE OPERATIONS
|
|
* ======================
|
|
*/
|
|
|
|
/*
|
|
* float4abs - returns |arg1| (absolute value)
|
|
*/
|
|
Datum
|
|
float4abs(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
|
|
PG_RETURN_FLOAT4((float4) fabs(arg1));
|
|
}
|
|
|
|
/*
|
|
* float4um - returns -arg1 (unary minus)
|
|
*/
|
|
Datum
|
|
float4um(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 result;
|
|
|
|
result = -arg1;
|
|
PG_RETURN_FLOAT4(result);
|
|
}
|
|
|
|
Datum
|
|
float4up(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg = PG_GETARG_FLOAT4(0);
|
|
|
|
PG_RETURN_FLOAT4(arg);
|
|
}
|
|
|
|
Datum
|
|
float4larger(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
float4 result;
|
|
|
|
if (float4_gt(arg1, arg2))
|
|
result = arg1;
|
|
else
|
|
result = arg2;
|
|
PG_RETURN_FLOAT4(result);
|
|
}
|
|
|
|
Datum
|
|
float4smaller(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
float4 result;
|
|
|
|
if (float4_lt(arg1, arg2))
|
|
result = arg1;
|
|
else
|
|
result = arg2;
|
|
PG_RETURN_FLOAT4(result);
|
|
}
|
|
|
|
/*
|
|
* ======================
|
|
* FLOAT8 BASE OPERATIONS
|
|
* ======================
|
|
*/
|
|
|
|
/*
|
|
* float8abs - returns |arg1| (absolute value)
|
|
*/
|
|
Datum
|
|
float8abs(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_FLOAT8(fabs(arg1));
|
|
}
|
|
|
|
|
|
/*
|
|
* float8um - returns -arg1 (unary minus)
|
|
*/
|
|
Datum
|
|
float8um(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
result = -arg1;
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
Datum
|
|
float8up(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_FLOAT8(arg);
|
|
}
|
|
|
|
Datum
|
|
float8larger(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
float8 result;
|
|
|
|
if (float8_gt(arg1, arg2))
|
|
result = arg1;
|
|
else
|
|
result = arg2;
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
Datum
|
|
float8smaller(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
float8 result;
|
|
|
|
if (float8_lt(arg1, arg2))
|
|
result = arg1;
|
|
else
|
|
result = arg2;
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* ====================
|
|
* ARITHMETIC OPERATORS
|
|
* ====================
|
|
*/
|
|
|
|
/*
|
|
* float4pl - returns arg1 + arg2
|
|
* float4mi - returns arg1 - arg2
|
|
* float4mul - returns arg1 * arg2
|
|
* float4div - returns arg1 / arg2
|
|
*/
|
|
Datum
|
|
float4pl(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT4(float4_pl(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4mi(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT4(float4_mi(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4mul(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT4(float4_mul(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4div(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT4(float4_div(arg1, arg2));
|
|
}
|
|
|
|
/*
|
|
* float8pl - returns arg1 + arg2
|
|
* float8mi - returns arg1 - arg2
|
|
* float8mul - returns arg1 * arg2
|
|
* float8div - returns arg1 / arg2
|
|
*/
|
|
Datum
|
|
float8pl(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_pl(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8mi(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_mi(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8mul(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_mul(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8div(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_div(arg1, arg2));
|
|
}
|
|
|
|
|
|
/*
|
|
* ====================
|
|
* COMPARISON OPERATORS
|
|
* ====================
|
|
*/
|
|
|
|
/*
|
|
* float4{eq,ne,lt,le,gt,ge} - float4/float4 comparison operations
|
|
*/
|
|
int
|
|
float4_cmp_internal(float4 a, float4 b)
|
|
{
|
|
if (float4_gt(a, b))
|
|
return 1;
|
|
if (float4_lt(a, b))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
Datum
|
|
float4eq(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float4_eq(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4ne(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float4_ne(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4lt(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float4_lt(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4le(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float4_le(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4gt(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float4_gt(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float4ge(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float4_ge(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
btfloat4cmp(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_INT32(float4_cmp_internal(arg1, arg2));
|
|
}
|
|
|
|
static int
|
|
btfloat4fastcmp(Datum x, Datum y, SortSupport ssup)
|
|
{
|
|
float4 arg1 = DatumGetFloat4(x);
|
|
float4 arg2 = DatumGetFloat4(y);
|
|
|
|
return float4_cmp_internal(arg1, arg2);
|
|
}
|
|
|
|
Datum
|
|
btfloat4sortsupport(PG_FUNCTION_ARGS)
|
|
{
|
|
SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
|
|
|
|
ssup->comparator = btfloat4fastcmp;
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/*
|
|
* float8{eq,ne,lt,le,gt,ge} - float8/float8 comparison operations
|
|
*/
|
|
int
|
|
float8_cmp_internal(float8 a, float8 b)
|
|
{
|
|
if (float8_gt(a, b))
|
|
return 1;
|
|
if (float8_lt(a, b))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
Datum
|
|
float8eq(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_eq(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8ne(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_ne(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8lt(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_lt(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8le(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_le(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8gt(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_gt(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float8ge(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_ge(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
btfloat8cmp(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_INT32(float8_cmp_internal(arg1, arg2));
|
|
}
|
|
|
|
static int
|
|
btfloat8fastcmp(Datum x, Datum y, SortSupport ssup)
|
|
{
|
|
float8 arg1 = DatumGetFloat8(x);
|
|
float8 arg2 = DatumGetFloat8(y);
|
|
|
|
return float8_cmp_internal(arg1, arg2);
|
|
}
|
|
|
|
Datum
|
|
btfloat8sortsupport(PG_FUNCTION_ARGS)
|
|
{
|
|
SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
|
|
|
|
ssup->comparator = btfloat8fastcmp;
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
Datum
|
|
btfloat48cmp(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
/* widen float4 to float8 and then compare */
|
|
PG_RETURN_INT32(float8_cmp_internal(arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
btfloat84cmp(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
/* widen float4 to float8 and then compare */
|
|
PG_RETURN_INT32(float8_cmp_internal(arg1, arg2));
|
|
}
|
|
|
|
/*
|
|
* in_range support function for float8.
|
|
*
|
|
* Note: we needn't supply a float8_float4 variant, as implicit coercion
|
|
* of the offset value takes care of that scenario just as well.
|
|
*/
|
|
Datum
|
|
in_range_float8_float8(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 val = PG_GETARG_FLOAT8(0);
|
|
float8 base = PG_GETARG_FLOAT8(1);
|
|
float8 offset = PG_GETARG_FLOAT8(2);
|
|
bool sub = PG_GETARG_BOOL(3);
|
|
bool less = PG_GETARG_BOOL(4);
|
|
float8 sum;
|
|
|
|
/*
|
|
* Reject negative or NaN offset. Negative is per spec, and NaN is
|
|
* because appropriate semantics for that seem non-obvious.
|
|
*/
|
|
if (isnan(offset) || offset < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
|
|
errmsg("invalid preceding or following size in window function")));
|
|
|
|
/*
|
|
* Deal with cases where val and/or base is NaN, following the rule that
|
|
* NaN sorts after non-NaN (cf float8_cmp_internal). The offset cannot
|
|
* affect the conclusion.
|
|
*/
|
|
if (isnan(val))
|
|
{
|
|
if (isnan(base))
|
|
PG_RETURN_BOOL(true); /* NAN = NAN */
|
|
else
|
|
PG_RETURN_BOOL(!less); /* NAN > non-NAN */
|
|
}
|
|
else if (isnan(base))
|
|
{
|
|
PG_RETURN_BOOL(less); /* non-NAN < NAN */
|
|
}
|
|
|
|
/*
|
|
* Deal with infinite offset (necessarily +inf, at this point). We must
|
|
* special-case this because if base happens to be -inf, their sum would
|
|
* be NaN, which is an overflow-ish condition we should avoid.
|
|
*/
|
|
if (isinf(offset))
|
|
{
|
|
PG_RETURN_BOOL(sub ? !less : less);
|
|
}
|
|
|
|
/*
|
|
* Otherwise it should be safe to compute base +/- offset. We trust the
|
|
* FPU to cope if base is +/-inf or the true sum would overflow, and
|
|
* produce a suitably signed infinity, which will compare properly against
|
|
* val whether or not that's infinity.
|
|
*/
|
|
if (sub)
|
|
sum = base - offset;
|
|
else
|
|
sum = base + offset;
|
|
|
|
if (less)
|
|
PG_RETURN_BOOL(val <= sum);
|
|
else
|
|
PG_RETURN_BOOL(val >= sum);
|
|
}
|
|
|
|
/*
|
|
* in_range support function for float4.
|
|
*
|
|
* We would need a float4_float8 variant in any case, so we supply that and
|
|
* let implicit coercion take care of the float4_float4 case.
|
|
*/
|
|
Datum
|
|
in_range_float4_float8(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 val = PG_GETARG_FLOAT4(0);
|
|
float4 base = PG_GETARG_FLOAT4(1);
|
|
float8 offset = PG_GETARG_FLOAT8(2);
|
|
bool sub = PG_GETARG_BOOL(3);
|
|
bool less = PG_GETARG_BOOL(4);
|
|
float8 sum;
|
|
|
|
/*
|
|
* Reject negative or NaN offset. Negative is per spec, and NaN is
|
|
* because appropriate semantics for that seem non-obvious.
|
|
*/
|
|
if (isnan(offset) || offset < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
|
|
errmsg("invalid preceding or following size in window function")));
|
|
|
|
/*
|
|
* Deal with cases where val and/or base is NaN, following the rule that
|
|
* NaN sorts after non-NaN (cf float8_cmp_internal). The offset cannot
|
|
* affect the conclusion.
|
|
*/
|
|
if (isnan(val))
|
|
{
|
|
if (isnan(base))
|
|
PG_RETURN_BOOL(true); /* NAN = NAN */
|
|
else
|
|
PG_RETURN_BOOL(!less); /* NAN > non-NAN */
|
|
}
|
|
else if (isnan(base))
|
|
{
|
|
PG_RETURN_BOOL(less); /* non-NAN < NAN */
|
|
}
|
|
|
|
/*
|
|
* Deal with infinite offset (necessarily +inf, at this point). We must
|
|
* special-case this because if base happens to be -inf, their sum would
|
|
* be NaN, which is an overflow-ish condition we should avoid.
|
|
*/
|
|
if (isinf(offset))
|
|
{
|
|
PG_RETURN_BOOL(sub ? !less : less);
|
|
}
|
|
|
|
/*
|
|
* Otherwise it should be safe to compute base +/- offset. We trust the
|
|
* FPU to cope if base is +/-inf or the true sum would overflow, and
|
|
* produce a suitably signed infinity, which will compare properly against
|
|
* val whether or not that's infinity.
|
|
*/
|
|
if (sub)
|
|
sum = base - offset;
|
|
else
|
|
sum = base + offset;
|
|
|
|
if (less)
|
|
PG_RETURN_BOOL(val <= sum);
|
|
else
|
|
PG_RETURN_BOOL(val >= sum);
|
|
}
|
|
|
|
|
|
/*
|
|
* ===================
|
|
* CONVERSION ROUTINES
|
|
* ===================
|
|
*/
|
|
|
|
/*
|
|
* ftod - converts a float4 number to a float8 number
|
|
*/
|
|
Datum
|
|
ftod(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 num = PG_GETARG_FLOAT4(0);
|
|
|
|
PG_RETURN_FLOAT8((float8) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* dtof - converts a float8 number to a float4 number
|
|
*/
|
|
Datum
|
|
dtof(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 num = PG_GETARG_FLOAT8(0);
|
|
float4 result;
|
|
|
|
result = (float4) num;
|
|
if (unlikely(isinf(result)) && !isinf(num))
|
|
float_overflow_error();
|
|
if (unlikely(result == 0.0f) && num != 0.0)
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT4(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dtoi4 - converts a float8 number to an int4 number
|
|
*/
|
|
Datum
|
|
dtoi4(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 num = PG_GETARG_FLOAT8(0);
|
|
|
|
/*
|
|
* Get rid of any fractional part in the input. This is so we don't fail
|
|
* on just-out-of-range values that would round into range. Note
|
|
* assumption that rint() will pass through a NaN or Inf unchanged.
|
|
*/
|
|
num = rint(num);
|
|
|
|
/* Range check */
|
|
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT32(num)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("integer out of range")));
|
|
|
|
PG_RETURN_INT32((int32) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* dtoi2 - converts a float8 number to an int2 number
|
|
*/
|
|
Datum
|
|
dtoi2(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 num = PG_GETARG_FLOAT8(0);
|
|
|
|
/*
|
|
* Get rid of any fractional part in the input. This is so we don't fail
|
|
* on just-out-of-range values that would round into range. Note
|
|
* assumption that rint() will pass through a NaN or Inf unchanged.
|
|
*/
|
|
num = rint(num);
|
|
|
|
/* Range check */
|
|
if (unlikely(isnan(num) || !FLOAT8_FITS_IN_INT16(num)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("smallint out of range")));
|
|
|
|
PG_RETURN_INT16((int16) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* i4tod - converts an int4 number to a float8 number
|
|
*/
|
|
Datum
|
|
i4tod(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 num = PG_GETARG_INT32(0);
|
|
|
|
PG_RETURN_FLOAT8((float8) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* i2tod - converts an int2 number to a float8 number
|
|
*/
|
|
Datum
|
|
i2tod(PG_FUNCTION_ARGS)
|
|
{
|
|
int16 num = PG_GETARG_INT16(0);
|
|
|
|
PG_RETURN_FLOAT8((float8) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* ftoi4 - converts a float4 number to an int4 number
|
|
*/
|
|
Datum
|
|
ftoi4(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 num = PG_GETARG_FLOAT4(0);
|
|
|
|
/*
|
|
* Get rid of any fractional part in the input. This is so we don't fail
|
|
* on just-out-of-range values that would round into range. Note
|
|
* assumption that rint() will pass through a NaN or Inf unchanged.
|
|
*/
|
|
num = rint(num);
|
|
|
|
/* Range check */
|
|
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT32(num)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("integer out of range")));
|
|
|
|
PG_RETURN_INT32((int32) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* ftoi2 - converts a float4 number to an int2 number
|
|
*/
|
|
Datum
|
|
ftoi2(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 num = PG_GETARG_FLOAT4(0);
|
|
|
|
/*
|
|
* Get rid of any fractional part in the input. This is so we don't fail
|
|
* on just-out-of-range values that would round into range. Note
|
|
* assumption that rint() will pass through a NaN or Inf unchanged.
|
|
*/
|
|
num = rint(num);
|
|
|
|
/* Range check */
|
|
if (unlikely(isnan(num) || !FLOAT4_FITS_IN_INT16(num)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("smallint out of range")));
|
|
|
|
PG_RETURN_INT16((int16) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* i4tof - converts an int4 number to a float4 number
|
|
*/
|
|
Datum
|
|
i4tof(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 num = PG_GETARG_INT32(0);
|
|
|
|
PG_RETURN_FLOAT4((float4) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* i2tof - converts an int2 number to a float4 number
|
|
*/
|
|
Datum
|
|
i2tof(PG_FUNCTION_ARGS)
|
|
{
|
|
int16 num = PG_GETARG_INT16(0);
|
|
|
|
PG_RETURN_FLOAT4((float4) num);
|
|
}
|
|
|
|
|
|
/*
|
|
* =======================
|
|
* RANDOM FLOAT8 OPERATORS
|
|
* =======================
|
|
*/
|
|
|
|
/*
|
|
* dround - returns ROUND(arg1)
|
|
*/
|
|
Datum
|
|
dround(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_FLOAT8(rint(arg1));
|
|
}
|
|
|
|
/*
|
|
* dceil - returns the smallest integer greater than or
|
|
* equal to the specified float
|
|
*/
|
|
Datum
|
|
dceil(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_FLOAT8(ceil(arg1));
|
|
}
|
|
|
|
/*
|
|
* dfloor - returns the largest integer lesser than or
|
|
* equal to the specified float
|
|
*/
|
|
Datum
|
|
dfloor(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_FLOAT8(floor(arg1));
|
|
}
|
|
|
|
/*
|
|
* dsign - returns -1 if the argument is less than 0, 0
|
|
* if the argument is equal to 0, and 1 if the
|
|
* argument is greater than zero.
|
|
*/
|
|
Datum
|
|
dsign(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
if (arg1 > 0)
|
|
result = 1.0;
|
|
else if (arg1 < 0)
|
|
result = -1.0;
|
|
else
|
|
result = 0.0;
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
/*
|
|
* dtrunc - returns truncation-towards-zero of arg1,
|
|
* arg1 >= 0 ... the greatest integer less
|
|
* than or equal to arg1
|
|
* arg1 < 0 ... the least integer greater
|
|
* than or equal to arg1
|
|
*/
|
|
Datum
|
|
dtrunc(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
if (arg1 >= 0)
|
|
result = floor(arg1);
|
|
else
|
|
result = -floor(-arg1);
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dsqrt - returns square root of arg1
|
|
*/
|
|
Datum
|
|
dsqrt(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
if (arg1 < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
|
|
errmsg("cannot take square root of a negative number")));
|
|
|
|
result = sqrt(arg1);
|
|
if (unlikely(isinf(result)) && !isinf(arg1))
|
|
float_overflow_error();
|
|
if (unlikely(result == 0.0) && arg1 != 0.0)
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dcbrt - returns cube root of arg1
|
|
*/
|
|
Datum
|
|
dcbrt(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
result = cbrt(arg1);
|
|
if (unlikely(isinf(result)) && !isinf(arg1))
|
|
float_overflow_error();
|
|
if (unlikely(result == 0.0) && arg1 != 0.0)
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dpow - returns pow(arg1,arg2)
|
|
*/
|
|
Datum
|
|
dpow(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
float8 result;
|
|
|
|
/*
|
|
* The POSIX spec says that NaN ^ 0 = 1, and 1 ^ NaN = 1, while all other
|
|
* cases with NaN inputs yield NaN (with no error). Many older platforms
|
|
* get one or more of these cases wrong, so deal with them via explicit
|
|
* logic rather than trusting pow(3).
|
|
*/
|
|
if (isnan(arg1))
|
|
{
|
|
if (isnan(arg2) || arg2 != 0.0)
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
PG_RETURN_FLOAT8(1.0);
|
|
}
|
|
if (isnan(arg2))
|
|
{
|
|
if (arg1 != 1.0)
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
PG_RETURN_FLOAT8(1.0);
|
|
}
|
|
|
|
/*
|
|
* The SQL spec requires that we emit a particular SQLSTATE error code for
|
|
* certain error conditions. Specifically, we don't return a
|
|
* divide-by-zero error code for 0 ^ -1.
|
|
*/
|
|
if (arg1 == 0 && arg2 < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
|
|
errmsg("zero raised to a negative power is undefined")));
|
|
if (arg1 < 0 && floor(arg2) != arg2)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
|
|
errmsg("a negative number raised to a non-integer power yields a complex result")));
|
|
|
|
/*
|
|
* pow() sets errno only on some platforms, depending on whether it
|
|
* follows _IEEE_, _POSIX_, _XOPEN_, or _SVID_, so we try to avoid using
|
|
* errno. However, some platform/CPU combinations return errno == EDOM
|
|
* and result == NaN for negative arg1 and very large arg2 (they must be
|
|
* using something different from our floor() test to decide it's
|
|
* invalid). Other platforms (HPPA) return errno == ERANGE and a large
|
|
* (HUGE_VAL) but finite result to signal overflow.
|
|
*/
|
|
errno = 0;
|
|
result = pow(arg1, arg2);
|
|
if (errno == EDOM && isnan(result))
|
|
{
|
|
if ((fabs(arg1) > 1 && arg2 >= 0) || (fabs(arg1) < 1 && arg2 < 0))
|
|
/* The sign of Inf is not significant in this case. */
|
|
result = get_float8_infinity();
|
|
else if (fabs(arg1) != 1)
|
|
result = 0;
|
|
else
|
|
result = 1;
|
|
}
|
|
else if (errno == ERANGE && result != 0 && !isinf(result))
|
|
result = get_float8_infinity();
|
|
|
|
if (unlikely(isinf(result)) && !isinf(arg1) && !isinf(arg2))
|
|
float_overflow_error();
|
|
if (unlikely(result == 0.0) && arg1 != 0.0)
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dexp - returns the exponential function of arg1
|
|
*/
|
|
Datum
|
|
dexp(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
errno = 0;
|
|
result = exp(arg1);
|
|
if (errno == ERANGE && result != 0 && !isinf(result))
|
|
result = get_float8_infinity();
|
|
|
|
if (unlikely(isinf(result)) && !isinf(arg1))
|
|
float_overflow_error();
|
|
if (unlikely(result == 0.0))
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dlog1 - returns the natural logarithm of arg1
|
|
*/
|
|
Datum
|
|
dlog1(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/*
|
|
* Emit particular SQLSTATE error codes for ln(). This is required by the
|
|
* SQL standard.
|
|
*/
|
|
if (arg1 == 0.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
|
|
errmsg("cannot take logarithm of zero")));
|
|
if (arg1 < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
|
|
errmsg("cannot take logarithm of a negative number")));
|
|
|
|
result = log(arg1);
|
|
if (unlikely(isinf(result)) && !isinf(arg1))
|
|
float_overflow_error();
|
|
if (unlikely(result == 0.0) && arg1 != 1.0)
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dlog10 - returns the base 10 logarithm of arg1
|
|
*/
|
|
Datum
|
|
dlog10(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/*
|
|
* Emit particular SQLSTATE error codes for log(). The SQL spec doesn't
|
|
* define log(), but it does define ln(), so it makes sense to emit the
|
|
* same error code for an analogous error condition.
|
|
*/
|
|
if (arg1 == 0.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
|
|
errmsg("cannot take logarithm of zero")));
|
|
if (arg1 < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
|
|
errmsg("cannot take logarithm of a negative number")));
|
|
|
|
result = log10(arg1);
|
|
if (unlikely(isinf(result)) && !isinf(arg1))
|
|
float_overflow_error();
|
|
if (unlikely(result == 0.0) && arg1 != 1.0)
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dacos - returns the arccos of arg1 (radians)
|
|
*/
|
|
Datum
|
|
dacos(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/*
|
|
* The principal branch of the inverse cosine function maps values in the
|
|
* range [-1, 1] to values in the range [0, Pi], so we should reject any
|
|
* inputs outside that range and the result will always be finite.
|
|
*/
|
|
if (arg1 < -1.0 || arg1 > 1.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
result = acos(arg1);
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dasin - returns the arcsin of arg1 (radians)
|
|
*/
|
|
Datum
|
|
dasin(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/*
|
|
* The principal branch of the inverse sine function maps values in the
|
|
* range [-1, 1] to values in the range [-Pi/2, Pi/2], so we should reject
|
|
* any inputs outside that range and the result will always be finite.
|
|
*/
|
|
if (arg1 < -1.0 || arg1 > 1.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
result = asin(arg1);
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* datan - returns the arctan of arg1 (radians)
|
|
*/
|
|
Datum
|
|
datan(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/*
|
|
* The principal branch of the inverse tangent function maps all inputs to
|
|
* values in the range [-Pi/2, Pi/2], so the result should always be
|
|
* finite, even if the input is infinite.
|
|
*/
|
|
result = atan(arg1);
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* atan2 - returns the arctan of arg1/arg2 (radians)
|
|
*/
|
|
Datum
|
|
datan2(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if either input is NaN */
|
|
if (isnan(arg1) || isnan(arg2))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/*
|
|
* atan2 maps all inputs to values in the range [-Pi, Pi], so the result
|
|
* should always be finite, even if the inputs are infinite.
|
|
*/
|
|
result = atan2(arg1, arg2);
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dcos - returns the cosine of arg1 (radians)
|
|
*/
|
|
Datum
|
|
dcos(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/*
|
|
* cos() is periodic and so theoretically can work for all finite inputs,
|
|
* but some implementations may choose to throw error if the input is so
|
|
* large that there are no significant digits in the result. So we should
|
|
* check for errors. POSIX allows an error to be reported either via
|
|
* errno or via fetestexcept(), but currently we only support checking
|
|
* errno. (fetestexcept() is rumored to report underflow unreasonably
|
|
* early on some platforms, so it's not clear that believing it would be a
|
|
* net improvement anyway.)
|
|
*
|
|
* For infinite inputs, POSIX specifies that the trigonometric functions
|
|
* should return a domain error; but we won't notice that unless the
|
|
* platform reports via errno, so also explicitly test for infinite
|
|
* inputs.
|
|
*/
|
|
errno = 0;
|
|
result = cos(arg1);
|
|
if (errno != 0 || isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dcot - returns the cotangent of arg1 (radians)
|
|
*/
|
|
Datum
|
|
dcot(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/* Be sure to throw an error if the input is infinite --- see dcos() */
|
|
errno = 0;
|
|
result = tan(arg1);
|
|
if (errno != 0 || isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
result = 1.0 / result;
|
|
/* Not checking for overflow because cot(0) == Inf */
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dsin - returns the sine of arg1 (radians)
|
|
*/
|
|
Datum
|
|
dsin(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/* Be sure to throw an error if the input is infinite --- see dcos() */
|
|
errno = 0;
|
|
result = sin(arg1);
|
|
if (errno != 0 || isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dtan - returns the tangent of arg1 (radians)
|
|
*/
|
|
Datum
|
|
dtan(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
/* Be sure to throw an error if the input is infinite --- see dcos() */
|
|
errno = 0;
|
|
result = tan(arg1);
|
|
if (errno != 0 || isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
/* Not checking for overflow because tan(pi/2) == Inf */
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/* ========== DEGREE-BASED TRIGONOMETRIC FUNCTIONS ========== */
|
|
|
|
|
|
/*
|
|
* Initialize the cached constants declared at the head of this file
|
|
* (sin_30 etc). The fact that we need those at all, let alone need this
|
|
* Rube-Goldberg-worthy method of initializing them, is because there are
|
|
* compilers out there that will precompute expressions such as sin(constant)
|
|
* using a sin() function different from what will be used at runtime. If we
|
|
* want exact results, we must ensure that none of the scaling constants used
|
|
* in the degree-based trig functions are computed that way. To do so, we
|
|
* compute them from the variables degree_c_thirty etc, which are also really
|
|
* constants, but the compiler cannot assume that.
|
|
*
|
|
* Other hazards we are trying to forestall with this kluge include the
|
|
* possibility that compilers will rearrange the expressions, or compute
|
|
* some intermediate results in registers wider than a standard double.
|
|
*
|
|
* In the places where we use these constants, the typical pattern is like
|
|
* volatile float8 sin_x = sin(x * RADIANS_PER_DEGREE);
|
|
* return (sin_x / sin_30);
|
|
* where we hope to get a value of exactly 1.0 from the division when x = 30.
|
|
* The volatile temporary variable is needed on machines with wide float
|
|
* registers, to ensure that the result of sin(x) is rounded to double width
|
|
* the same as the value of sin_30 has been. Experimentation with gcc shows
|
|
* that marking the temp variable volatile is necessary to make the store and
|
|
* reload actually happen; hopefully the same trick works for other compilers.
|
|
* (gcc's documentation suggests using the -ffloat-store compiler switch to
|
|
* ensure this, but that is compiler-specific and it also pessimizes code in
|
|
* many places where we don't care about this.)
|
|
*/
|
|
static void
|
|
init_degree_constants(void)
|
|
{
|
|
sin_30 = sin(degree_c_thirty * RADIANS_PER_DEGREE);
|
|
one_minus_cos_60 = 1.0 - cos(degree_c_sixty * RADIANS_PER_DEGREE);
|
|
asin_0_5 = asin(degree_c_one_half);
|
|
acos_0_5 = acos(degree_c_one_half);
|
|
atan_1_0 = atan(degree_c_one);
|
|
tan_45 = sind_q1(degree_c_forty_five) / cosd_q1(degree_c_forty_five);
|
|
cot_45 = cosd_q1(degree_c_forty_five) / sind_q1(degree_c_forty_five);
|
|
degree_consts_set = true;
|
|
}
|
|
|
|
#define INIT_DEGREE_CONSTANTS() \
|
|
do { \
|
|
if (!degree_consts_set) \
|
|
init_degree_constants(); \
|
|
} while(0)
|
|
|
|
|
|
/*
|
|
* asind_q1 - returns the inverse sine of x in degrees, for x in
|
|
* the range [0, 1]. The result is an angle in the
|
|
* first quadrant --- [0, 90] degrees.
|
|
*
|
|
* For the 3 special case inputs (0, 0.5 and 1), this
|
|
* function will return exact values (0, 30 and 90
|
|
* degrees respectively).
|
|
*/
|
|
static double
|
|
asind_q1(double x)
|
|
{
|
|
/*
|
|
* Stitch together inverse sine and cosine functions for the ranges [0,
|
|
* 0.5] and (0.5, 1]. Each expression below is guaranteed to return
|
|
* exactly 30 for x=0.5, so the result is a continuous monotonic function
|
|
* over the full range.
|
|
*/
|
|
if (x <= 0.5)
|
|
{
|
|
volatile float8 asin_x = asin(x);
|
|
|
|
return (asin_x / asin_0_5) * 30.0;
|
|
}
|
|
else
|
|
{
|
|
volatile float8 acos_x = acos(x);
|
|
|
|
return 90.0 - (acos_x / acos_0_5) * 60.0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* acosd_q1 - returns the inverse cosine of x in degrees, for x in
|
|
* the range [0, 1]. The result is an angle in the
|
|
* first quadrant --- [0, 90] degrees.
|
|
*
|
|
* For the 3 special case inputs (0, 0.5 and 1), this
|
|
* function will return exact values (0, 60 and 90
|
|
* degrees respectively).
|
|
*/
|
|
static double
|
|
acosd_q1(double x)
|
|
{
|
|
/*
|
|
* Stitch together inverse sine and cosine functions for the ranges [0,
|
|
* 0.5] and (0.5, 1]. Each expression below is guaranteed to return
|
|
* exactly 60 for x=0.5, so the result is a continuous monotonic function
|
|
* over the full range.
|
|
*/
|
|
if (x <= 0.5)
|
|
{
|
|
volatile float8 asin_x = asin(x);
|
|
|
|
return 90.0 - (asin_x / asin_0_5) * 30.0;
|
|
}
|
|
else
|
|
{
|
|
volatile float8 acos_x = acos(x);
|
|
|
|
return (acos_x / acos_0_5) * 60.0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* dacosd - returns the arccos of arg1 (degrees)
|
|
*/
|
|
Datum
|
|
dacosd(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/*
|
|
* The principal branch of the inverse cosine function maps values in the
|
|
* range [-1, 1] to values in the range [0, 180], so we should reject any
|
|
* inputs outside that range and the result will always be finite.
|
|
*/
|
|
if (arg1 < -1.0 || arg1 > 1.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
if (arg1 >= 0.0)
|
|
result = acosd_q1(arg1);
|
|
else
|
|
result = 90.0 + asind_q1(-arg1);
|
|
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dasind - returns the arcsin of arg1 (degrees)
|
|
*/
|
|
Datum
|
|
dasind(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/*
|
|
* The principal branch of the inverse sine function maps values in the
|
|
* range [-1, 1] to values in the range [-90, 90], so we should reject any
|
|
* inputs outside that range and the result will always be finite.
|
|
*/
|
|
if (arg1 < -1.0 || arg1 > 1.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
if (arg1 >= 0.0)
|
|
result = asind_q1(arg1);
|
|
else
|
|
result = -asind_q1(-arg1);
|
|
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* datand - returns the arctan of arg1 (degrees)
|
|
*/
|
|
Datum
|
|
datand(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
volatile float8 atan_arg1;
|
|
|
|
/* Per the POSIX spec, return NaN if the input is NaN */
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/*
|
|
* The principal branch of the inverse tangent function maps all inputs to
|
|
* values in the range [-90, 90], so the result should always be finite,
|
|
* even if the input is infinite. Additionally, we take care to ensure
|
|
* than when arg1 is 1, the result is exactly 45.
|
|
*/
|
|
atan_arg1 = atan(arg1);
|
|
result = (atan_arg1 / atan_1_0) * 45.0;
|
|
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* atan2d - returns the arctan of arg1/arg2 (degrees)
|
|
*/
|
|
Datum
|
|
datan2d(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
float8 result;
|
|
volatile float8 atan2_arg1_arg2;
|
|
|
|
/* Per the POSIX spec, return NaN if either input is NaN */
|
|
if (isnan(arg1) || isnan(arg2))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/*
|
|
* atan2d maps all inputs to values in the range [-180, 180], so the
|
|
* result should always be finite, even if the inputs are infinite.
|
|
*
|
|
* Note: this coding assumes that atan(1.0) is a suitable scaling constant
|
|
* to get an exact result from atan2(). This might well fail on us at
|
|
* some point, requiring us to decide exactly what inputs we think we're
|
|
* going to guarantee an exact result for.
|
|
*/
|
|
atan2_arg1_arg2 = atan2(arg1, arg2);
|
|
result = (atan2_arg1_arg2 / atan_1_0) * 45.0;
|
|
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* sind_0_to_30 - returns the sine of an angle that lies between 0 and
|
|
* 30 degrees. This will return exactly 0 when x is 0,
|
|
* and exactly 0.5 when x is 30 degrees.
|
|
*/
|
|
static double
|
|
sind_0_to_30(double x)
|
|
{
|
|
volatile float8 sin_x = sin(x * RADIANS_PER_DEGREE);
|
|
|
|
return (sin_x / sin_30) / 2.0;
|
|
}
|
|
|
|
|
|
/*
|
|
* cosd_0_to_60 - returns the cosine of an angle that lies between 0
|
|
* and 60 degrees. This will return exactly 1 when x
|
|
* is 0, and exactly 0.5 when x is 60 degrees.
|
|
*/
|
|
static double
|
|
cosd_0_to_60(double x)
|
|
{
|
|
volatile float8 one_minus_cos_x = 1.0 - cos(x * RADIANS_PER_DEGREE);
|
|
|
|
return 1.0 - (one_minus_cos_x / one_minus_cos_60) / 2.0;
|
|
}
|
|
|
|
|
|
/*
|
|
* sind_q1 - returns the sine of an angle in the first quadrant
|
|
* (0 to 90 degrees).
|
|
*/
|
|
static double
|
|
sind_q1(double x)
|
|
{
|
|
/*
|
|
* Stitch together the sine and cosine functions for the ranges [0, 30]
|
|
* and (30, 90]. These guarantee to return exact answers at their
|
|
* endpoints, so the overall result is a continuous monotonic function
|
|
* that gives exact results when x = 0, 30 and 90 degrees.
|
|
*/
|
|
if (x <= 30.0)
|
|
return sind_0_to_30(x);
|
|
else
|
|
return cosd_0_to_60(90.0 - x);
|
|
}
|
|
|
|
|
|
/*
|
|
* cosd_q1 - returns the cosine of an angle in the first quadrant
|
|
* (0 to 90 degrees).
|
|
*/
|
|
static double
|
|
cosd_q1(double x)
|
|
{
|
|
/*
|
|
* Stitch together the sine and cosine functions for the ranges [0, 60]
|
|
* and (60, 90]. These guarantee to return exact answers at their
|
|
* endpoints, so the overall result is a continuous monotonic function
|
|
* that gives exact results when x = 0, 60 and 90 degrees.
|
|
*/
|
|
if (x <= 60.0)
|
|
return cosd_0_to_60(x);
|
|
else
|
|
return sind_0_to_30(90.0 - x);
|
|
}
|
|
|
|
|
|
/*
|
|
* dcosd - returns the cosine of arg1 (degrees)
|
|
*/
|
|
Datum
|
|
dcosd(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
int sign = 1;
|
|
|
|
/*
|
|
* Per the POSIX spec, return NaN if the input is NaN and throw an error
|
|
* if the input is infinite.
|
|
*/
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
if (isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/* Reduce the range of the input to [0,90] degrees */
|
|
arg1 = fmod(arg1, 360.0);
|
|
|
|
if (arg1 < 0.0)
|
|
{
|
|
/* cosd(-x) = cosd(x) */
|
|
arg1 = -arg1;
|
|
}
|
|
|
|
if (arg1 > 180.0)
|
|
{
|
|
/* cosd(360-x) = cosd(x) */
|
|
arg1 = 360.0 - arg1;
|
|
}
|
|
|
|
if (arg1 > 90.0)
|
|
{
|
|
/* cosd(180-x) = -cosd(x) */
|
|
arg1 = 180.0 - arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
result = sign * cosd_q1(arg1);
|
|
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dcotd - returns the cotangent of arg1 (degrees)
|
|
*/
|
|
Datum
|
|
dcotd(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
volatile float8 cot_arg1;
|
|
int sign = 1;
|
|
|
|
/*
|
|
* Per the POSIX spec, return NaN if the input is NaN and throw an error
|
|
* if the input is infinite.
|
|
*/
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
if (isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/* Reduce the range of the input to [0,90] degrees */
|
|
arg1 = fmod(arg1, 360.0);
|
|
|
|
if (arg1 < 0.0)
|
|
{
|
|
/* cotd(-x) = -cotd(x) */
|
|
arg1 = -arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
if (arg1 > 180.0)
|
|
{
|
|
/* cotd(360-x) = -cotd(x) */
|
|
arg1 = 360.0 - arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
if (arg1 > 90.0)
|
|
{
|
|
/* cotd(180-x) = -cotd(x) */
|
|
arg1 = 180.0 - arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
cot_arg1 = cosd_q1(arg1) / sind_q1(arg1);
|
|
result = sign * (cot_arg1 / cot_45);
|
|
|
|
/*
|
|
* On some machines we get cotd(270) = minus zero, but this isn't always
|
|
* true. For portability, and because the user constituency for this
|
|
* function probably doesn't want minus zero, force it to plain zero.
|
|
*/
|
|
if (result == 0.0)
|
|
result = 0.0;
|
|
|
|
/* Not checking for overflow because cotd(0) == Inf */
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dsind - returns the sine of arg1 (degrees)
|
|
*/
|
|
Datum
|
|
dsind(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
int sign = 1;
|
|
|
|
/*
|
|
* Per the POSIX spec, return NaN if the input is NaN and throw an error
|
|
* if the input is infinite.
|
|
*/
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
if (isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/* Reduce the range of the input to [0,90] degrees */
|
|
arg1 = fmod(arg1, 360.0);
|
|
|
|
if (arg1 < 0.0)
|
|
{
|
|
/* sind(-x) = -sind(x) */
|
|
arg1 = -arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
if (arg1 > 180.0)
|
|
{
|
|
/* sind(360-x) = -sind(x) */
|
|
arg1 = 360.0 - arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
if (arg1 > 90.0)
|
|
{
|
|
/* sind(180-x) = sind(x) */
|
|
arg1 = 180.0 - arg1;
|
|
}
|
|
|
|
result = sign * sind_q1(arg1);
|
|
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dtand - returns the tangent of arg1 (degrees)
|
|
*/
|
|
Datum
|
|
dtand(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
volatile float8 tan_arg1;
|
|
int sign = 1;
|
|
|
|
/*
|
|
* Per the POSIX spec, return NaN if the input is NaN and throw an error
|
|
* if the input is infinite.
|
|
*/
|
|
if (isnan(arg1))
|
|
PG_RETURN_FLOAT8(get_float8_nan());
|
|
|
|
if (isinf(arg1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
INIT_DEGREE_CONSTANTS();
|
|
|
|
/* Reduce the range of the input to [0,90] degrees */
|
|
arg1 = fmod(arg1, 360.0);
|
|
|
|
if (arg1 < 0.0)
|
|
{
|
|
/* tand(-x) = -tand(x) */
|
|
arg1 = -arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
if (arg1 > 180.0)
|
|
{
|
|
/* tand(360-x) = -tand(x) */
|
|
arg1 = 360.0 - arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
if (arg1 > 90.0)
|
|
{
|
|
/* tand(180-x) = -tand(x) */
|
|
arg1 = 180.0 - arg1;
|
|
sign = -sign;
|
|
}
|
|
|
|
tan_arg1 = sind_q1(arg1) / cosd_q1(arg1);
|
|
result = sign * (tan_arg1 / tan_45);
|
|
|
|
/*
|
|
* On some machines we get tand(180) = minus zero, but this isn't always
|
|
* true. For portability, and because the user constituency for this
|
|
* function probably doesn't want minus zero, force it to plain zero.
|
|
*/
|
|
if (result == 0.0)
|
|
result = 0.0;
|
|
|
|
/* Not checking for overflow because tand(90) == Inf */
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* degrees - returns degrees converted from radians
|
|
*/
|
|
Datum
|
|
degrees(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_FLOAT8(float8_div(arg1, RADIANS_PER_DEGREE));
|
|
}
|
|
|
|
|
|
/*
|
|
* dpi - returns the constant PI
|
|
*/
|
|
Datum
|
|
dpi(PG_FUNCTION_ARGS)
|
|
{
|
|
PG_RETURN_FLOAT8(M_PI);
|
|
}
|
|
|
|
|
|
/*
|
|
* radians - returns radians converted from degrees
|
|
*/
|
|
Datum
|
|
radians(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
|
|
PG_RETURN_FLOAT8(float8_mul(arg1, RADIANS_PER_DEGREE));
|
|
}
|
|
|
|
|
|
/* ========== HYPERBOLIC FUNCTIONS ========== */
|
|
|
|
|
|
/*
|
|
* dsinh - returns the hyperbolic sine of arg1
|
|
*/
|
|
Datum
|
|
dsinh(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
errno = 0;
|
|
result = sinh(arg1);
|
|
|
|
/*
|
|
* if an ERANGE error occurs, it means there is an overflow. For sinh,
|
|
* the result should be either -infinity or infinity, depending on the
|
|
* sign of arg1.
|
|
*/
|
|
if (errno == ERANGE)
|
|
{
|
|
if (arg1 < 0)
|
|
result = -get_float8_infinity();
|
|
else
|
|
result = get_float8_infinity();
|
|
}
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* dcosh - returns the hyperbolic cosine of arg1
|
|
*/
|
|
Datum
|
|
dcosh(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
errno = 0;
|
|
result = cosh(arg1);
|
|
|
|
/*
|
|
* if an ERANGE error occurs, it means there is an overflow. As cosh is
|
|
* always positive, it always means the result is positive infinity.
|
|
*/
|
|
if (errno == ERANGE)
|
|
result = get_float8_infinity();
|
|
|
|
if (unlikely(result == 0.0))
|
|
float_underflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
/*
|
|
* dtanh - returns the hyperbolic tangent of arg1
|
|
*/
|
|
Datum
|
|
dtanh(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/*
|
|
* For tanh, we don't need an errno check because it never overflows.
|
|
*/
|
|
result = tanh(arg1);
|
|
|
|
if (unlikely(isinf(result)))
|
|
float_overflow_error();
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
/*
|
|
* dasinh - returns the inverse hyperbolic sine of arg1
|
|
*/
|
|
Datum
|
|
dasinh(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/*
|
|
* For asinh, we don't need an errno check because it never overflows.
|
|
*/
|
|
result = asinh(arg1);
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
/*
|
|
* dacosh - returns the inverse hyperbolic cosine of arg1
|
|
*/
|
|
Datum
|
|
dacosh(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/*
|
|
* acosh is only defined for inputs >= 1.0. By checking this ourselves,
|
|
* we need not worry about checking for an EDOM error, which is a good
|
|
* thing because some implementations will report that for NaN. Otherwise,
|
|
* no error is possible.
|
|
*/
|
|
if (arg1 < 1.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
result = acosh(arg1);
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
/*
|
|
* datanh - returns the inverse hyperbolic tangent of arg1
|
|
*/
|
|
Datum
|
|
datanh(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float8 result;
|
|
|
|
/*
|
|
* atanh is only defined for inputs between -1 and 1. By checking this
|
|
* ourselves, we need not worry about checking for an EDOM error, which is
|
|
* a good thing because some implementations will report that for NaN.
|
|
*/
|
|
if (arg1 < -1.0 || arg1 > 1.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("input is out of range")));
|
|
|
|
/*
|
|
* Also handle the infinity cases ourselves; this is helpful because old
|
|
* glibc versions may produce the wrong errno for this. All other inputs
|
|
* cannot produce an error.
|
|
*/
|
|
if (arg1 == -1.0)
|
|
result = -get_float8_infinity();
|
|
else if (arg1 == 1.0)
|
|
result = get_float8_infinity();
|
|
else
|
|
result = atanh(arg1);
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* drandom - returns a random number
|
|
*/
|
|
Datum
|
|
drandom(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 result;
|
|
|
|
/* Initialize random seed, if not done yet in this process */
|
|
if (unlikely(!drandom_seed_set))
|
|
{
|
|
/*
|
|
* If possible, initialize the seed using high-quality random bits.
|
|
* Should that fail for some reason, we fall back on a lower-quality
|
|
* seed based on current time and PID.
|
|
*/
|
|
if (!pg_strong_random(drandom_seed, sizeof(drandom_seed)))
|
|
{
|
|
TimestampTz now = GetCurrentTimestamp();
|
|
uint64 iseed;
|
|
|
|
/* Mix the PID with the most predictable bits of the timestamp */
|
|
iseed = (uint64) now ^ ((uint64) MyProcPid << 32);
|
|
drandom_seed[0] = (unsigned short) iseed;
|
|
drandom_seed[1] = (unsigned short) (iseed >> 16);
|
|
drandom_seed[2] = (unsigned short) (iseed >> 32);
|
|
}
|
|
drandom_seed_set = true;
|
|
}
|
|
|
|
/* pg_erand48 produces desired result range [0.0 - 1.0) */
|
|
result = pg_erand48(drandom_seed);
|
|
|
|
PG_RETURN_FLOAT8(result);
|
|
}
|
|
|
|
|
|
/*
|
|
* setseed - set seed for the random number generator
|
|
*/
|
|
Datum
|
|
setseed(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 seed = PG_GETARG_FLOAT8(0);
|
|
uint64 iseed;
|
|
|
|
if (seed < -1 || seed > 1 || isnan(seed))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("setseed parameter %g is out of allowed range [-1,1]",
|
|
seed)));
|
|
|
|
/* Use sign bit + 47 fractional bits to fill drandom_seed[] */
|
|
iseed = (int64) (seed * (float8) UINT64CONST(0x7FFFFFFFFFFF));
|
|
drandom_seed[0] = (unsigned short) iseed;
|
|
drandom_seed[1] = (unsigned short) (iseed >> 16);
|
|
drandom_seed[2] = (unsigned short) (iseed >> 32);
|
|
drandom_seed_set = true;
|
|
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* =========================
|
|
* FLOAT AGGREGATE OPERATORS
|
|
* =========================
|
|
*
|
|
* float8_accum - accumulate for AVG(), variance aggregates, etc.
|
|
* float4_accum - same, but input data is float4
|
|
* float8_avg - produce final result for float AVG()
|
|
* float8_var_samp - produce final result for float VAR_SAMP()
|
|
* float8_var_pop - produce final result for float VAR_POP()
|
|
* float8_stddev_samp - produce final result for float STDDEV_SAMP()
|
|
* float8_stddev_pop - produce final result for float STDDEV_POP()
|
|
*
|
|
* The naive schoolbook implementation of these aggregates works by
|
|
* accumulating sum(X) and sum(X^2). However, this approach suffers from
|
|
* large rounding errors in the final computation of quantities like the
|
|
* population variance (N*sum(X^2) - sum(X)^2) / N^2, since each of the
|
|
* intermediate terms is potentially very large, while the difference is often
|
|
* quite small.
|
|
*
|
|
* Instead we use the Youngs-Cramer algorithm [1] which works by accumulating
|
|
* Sx=sum(X) and Sxx=sum((X-Sx/N)^2), using a numerically stable algorithm to
|
|
* incrementally update those quantities. The final computations of each of
|
|
* the aggregate values is then trivial and gives more accurate results (for
|
|
* example, the population variance is just Sxx/N). This algorithm is also
|
|
* fairly easy to generalize to allow parallel execution without loss of
|
|
* precision (see, for example, [2]). For more details, and a comparison of
|
|
* this with other algorithms, see [3].
|
|
*
|
|
* The transition datatype for all these aggregates is a 3-element array
|
|
* of float8, holding the values N, Sx, Sxx in that order.
|
|
*
|
|
* Note that we represent N as a float to avoid having to build a special
|
|
* datatype. Given a reasonable floating-point implementation, there should
|
|
* be no accuracy loss unless N exceeds 2 ^ 52 or so (by which time the
|
|
* user will have doubtless lost interest anyway...)
|
|
*
|
|
* [1] Some Results Relevant to Choice of Sum and Sum-of-Product Algorithms,
|
|
* E. A. Youngs and E. M. Cramer, Technometrics Vol 13, No 3, August 1971.
|
|
*
|
|
* [2] Updating Formulae and a Pairwise Algorithm for Computing Sample
|
|
* Variances, T. F. Chan, G. H. Golub & R. J. LeVeque, COMPSTAT 1982.
|
|
*
|
|
* [3] Numerically Stable Parallel Computation of (Co-)Variance, Erich
|
|
* Schubert and Michael Gertz, Proceedings of the 30th International
|
|
* Conference on Scientific and Statistical Database Management, 2018.
|
|
*/
|
|
|
|
static float8 *
|
|
check_float8_array(ArrayType *transarray, const char *caller, int n)
|
|
{
|
|
/*
|
|
* We expect the input to be an N-element float array; verify that. We
|
|
* don't need to use deconstruct_array() since the array data is just
|
|
* going to look like a C array of N float8 values.
|
|
*/
|
|
if (ARR_NDIM(transarray) != 1 ||
|
|
ARR_DIMS(transarray)[0] != n ||
|
|
ARR_HASNULL(transarray) ||
|
|
ARR_ELEMTYPE(transarray) != FLOAT8OID)
|
|
elog(ERROR, "%s: expected %d-element float8 array", caller, n);
|
|
return (float8 *) ARR_DATA_PTR(transarray);
|
|
}
|
|
|
|
/*
|
|
* float8_combine
|
|
*
|
|
* An aggregate combine function used to combine two 3 fields
|
|
* aggregate transition data into a single transition data.
|
|
* This function is used only in two stage aggregation and
|
|
* shouldn't be called outside aggregate context.
|
|
*/
|
|
Datum
|
|
float8_combine(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray1 = PG_GETARG_ARRAYTYPE_P(0);
|
|
ArrayType *transarray2 = PG_GETARG_ARRAYTYPE_P(1);
|
|
float8 *transvalues1;
|
|
float8 *transvalues2;
|
|
float8 N1,
|
|
Sx1,
|
|
Sxx1,
|
|
N2,
|
|
Sx2,
|
|
Sxx2,
|
|
tmp,
|
|
N,
|
|
Sx,
|
|
Sxx;
|
|
|
|
transvalues1 = check_float8_array(transarray1, "float8_combine", 3);
|
|
transvalues2 = check_float8_array(transarray2, "float8_combine", 3);
|
|
|
|
N1 = transvalues1[0];
|
|
Sx1 = transvalues1[1];
|
|
Sxx1 = transvalues1[2];
|
|
|
|
N2 = transvalues2[0];
|
|
Sx2 = transvalues2[1];
|
|
Sxx2 = transvalues2[2];
|
|
|
|
/*--------------------
|
|
* The transition values combine using a generalization of the
|
|
* Youngs-Cramer algorithm as follows:
|
|
*
|
|
* N = N1 + N2
|
|
* Sx = Sx1 + Sx2
|
|
* Sxx = Sxx1 + Sxx2 + N1 * N2 * (Sx1/N1 - Sx2/N2)^2 / N;
|
|
*
|
|
* It's worth handling the special cases N1 = 0 and N2 = 0 separately
|
|
* since those cases are trivial, and we then don't need to worry about
|
|
* division-by-zero errors in the general case.
|
|
*--------------------
|
|
*/
|
|
if (N1 == 0.0)
|
|
{
|
|
N = N2;
|
|
Sx = Sx2;
|
|
Sxx = Sxx2;
|
|
}
|
|
else if (N2 == 0.0)
|
|
{
|
|
N = N1;
|
|
Sx = Sx1;
|
|
Sxx = Sxx1;
|
|
}
|
|
else
|
|
{
|
|
N = N1 + N2;
|
|
Sx = float8_pl(Sx1, Sx2);
|
|
tmp = Sx1 / N1 - Sx2 / N2;
|
|
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp * tmp / N;
|
|
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
|
|
float_overflow_error();
|
|
}
|
|
|
|
/*
|
|
* If we're invoked as an aggregate, we can cheat and modify our first
|
|
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
|
* new array with the updated transition data and return it.
|
|
*/
|
|
if (AggCheckCallContext(fcinfo, NULL))
|
|
{
|
|
transvalues1[0] = N;
|
|
transvalues1[1] = Sx;
|
|
transvalues1[2] = Sxx;
|
|
|
|
PG_RETURN_ARRAYTYPE_P(transarray1);
|
|
}
|
|
else
|
|
{
|
|
Datum transdatums[3];
|
|
ArrayType *result;
|
|
|
|
transdatums[0] = Float8GetDatumFast(N);
|
|
transdatums[1] = Float8GetDatumFast(Sx);
|
|
transdatums[2] = Float8GetDatumFast(Sxx);
|
|
|
|
result = construct_array(transdatums, 3,
|
|
FLOAT8OID,
|
|
sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
}
|
|
|
|
Datum
|
|
float8_accum(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 newval = PG_GETARG_FLOAT8(1);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sx,
|
|
Sxx,
|
|
tmp;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_accum", 3);
|
|
N = transvalues[0];
|
|
Sx = transvalues[1];
|
|
Sxx = transvalues[2];
|
|
|
|
/*
|
|
* Use the Youngs-Cramer algorithm to incorporate the new value into the
|
|
* transition values.
|
|
*/
|
|
N += 1.0;
|
|
Sx += newval;
|
|
if (transvalues[0] > 0.0)
|
|
{
|
|
tmp = newval * N - Sx;
|
|
Sxx += tmp * tmp / (N * transvalues[0]);
|
|
|
|
/*
|
|
* Overflow check. We only report an overflow error when finite
|
|
* inputs lead to infinite results. Note also that Sxx should be NaN
|
|
* if any of the inputs are infinite, so we intentionally prevent Sxx
|
|
* from becoming infinite.
|
|
*/
|
|
if (isinf(Sx) || isinf(Sxx))
|
|
{
|
|
if (!isinf(transvalues[1]) && !isinf(newval))
|
|
float_overflow_error();
|
|
|
|
Sxx = get_float8_nan();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* At the first input, we normally can leave Sxx as 0. However, if
|
|
* the first input is Inf or NaN, we'd better force Sxx to NaN;
|
|
* otherwise we will falsely report variance zero when there are no
|
|
* more inputs.
|
|
*/
|
|
if (isnan(newval) || isinf(newval))
|
|
Sxx = get_float8_nan();
|
|
}
|
|
|
|
/*
|
|
* If we're invoked as an aggregate, we can cheat and modify our first
|
|
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
|
* new array with the updated transition data and return it.
|
|
*/
|
|
if (AggCheckCallContext(fcinfo, NULL))
|
|
{
|
|
transvalues[0] = N;
|
|
transvalues[1] = Sx;
|
|
transvalues[2] = Sxx;
|
|
|
|
PG_RETURN_ARRAYTYPE_P(transarray);
|
|
}
|
|
else
|
|
{
|
|
Datum transdatums[3];
|
|
ArrayType *result;
|
|
|
|
transdatums[0] = Float8GetDatumFast(N);
|
|
transdatums[1] = Float8GetDatumFast(Sx);
|
|
transdatums[2] = Float8GetDatumFast(Sxx);
|
|
|
|
result = construct_array(transdatums, 3,
|
|
FLOAT8OID,
|
|
sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
}
|
|
|
|
Datum
|
|
float4_accum(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
|
|
/* do computations as float8 */
|
|
float8 newval = PG_GETARG_FLOAT4(1);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sx,
|
|
Sxx,
|
|
tmp;
|
|
|
|
transvalues = check_float8_array(transarray, "float4_accum", 3);
|
|
N = transvalues[0];
|
|
Sx = transvalues[1];
|
|
Sxx = transvalues[2];
|
|
|
|
/*
|
|
* Use the Youngs-Cramer algorithm to incorporate the new value into the
|
|
* transition values.
|
|
*/
|
|
N += 1.0;
|
|
Sx += newval;
|
|
if (transvalues[0] > 0.0)
|
|
{
|
|
tmp = newval * N - Sx;
|
|
Sxx += tmp * tmp / (N * transvalues[0]);
|
|
|
|
/*
|
|
* Overflow check. We only report an overflow error when finite
|
|
* inputs lead to infinite results. Note also that Sxx should be NaN
|
|
* if any of the inputs are infinite, so we intentionally prevent Sxx
|
|
* from becoming infinite.
|
|
*/
|
|
if (isinf(Sx) || isinf(Sxx))
|
|
{
|
|
if (!isinf(transvalues[1]) && !isinf(newval))
|
|
float_overflow_error();
|
|
|
|
Sxx = get_float8_nan();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* At the first input, we normally can leave Sxx as 0. However, if
|
|
* the first input is Inf or NaN, we'd better force Sxx to NaN;
|
|
* otherwise we will falsely report variance zero when there are no
|
|
* more inputs.
|
|
*/
|
|
if (isnan(newval) || isinf(newval))
|
|
Sxx = get_float8_nan();
|
|
}
|
|
|
|
/*
|
|
* If we're invoked as an aggregate, we can cheat and modify our first
|
|
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
|
* new array with the updated transition data and return it.
|
|
*/
|
|
if (AggCheckCallContext(fcinfo, NULL))
|
|
{
|
|
transvalues[0] = N;
|
|
transvalues[1] = Sx;
|
|
transvalues[2] = Sxx;
|
|
|
|
PG_RETURN_ARRAYTYPE_P(transarray);
|
|
}
|
|
else
|
|
{
|
|
Datum transdatums[3];
|
|
ArrayType *result;
|
|
|
|
transdatums[0] = Float8GetDatumFast(N);
|
|
transdatums[1] = Float8GetDatumFast(Sx);
|
|
transdatums[2] = Float8GetDatumFast(Sxx);
|
|
|
|
result = construct_array(transdatums, 3,
|
|
FLOAT8OID,
|
|
sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
}
|
|
|
|
Datum
|
|
float8_avg(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sx;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_avg", 3);
|
|
N = transvalues[0];
|
|
Sx = transvalues[1];
|
|
/* ignore Sxx */
|
|
|
|
/* SQL defines AVG of no values to be NULL */
|
|
if (N == 0.0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8(Sx / N);
|
|
}
|
|
|
|
Datum
|
|
float8_var_pop(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_var_pop", 3);
|
|
N = transvalues[0];
|
|
/* ignore Sx */
|
|
Sxx = transvalues[2];
|
|
|
|
/* Population variance is undefined when N is 0, so return NULL */
|
|
if (N == 0.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx is guaranteed to be non-negative */
|
|
|
|
PG_RETURN_FLOAT8(Sxx / N);
|
|
}
|
|
|
|
Datum
|
|
float8_var_samp(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_var_samp", 3);
|
|
N = transvalues[0];
|
|
/* ignore Sx */
|
|
Sxx = transvalues[2];
|
|
|
|
/* Sample variance is undefined when N is 0 or 1, so return NULL */
|
|
if (N <= 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx is guaranteed to be non-negative */
|
|
|
|
PG_RETURN_FLOAT8(Sxx / (N - 1.0));
|
|
}
|
|
|
|
Datum
|
|
float8_stddev_pop(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_stddev_pop", 3);
|
|
N = transvalues[0];
|
|
/* ignore Sx */
|
|
Sxx = transvalues[2];
|
|
|
|
/* Population stddev is undefined when N is 0, so return NULL */
|
|
if (N == 0.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx is guaranteed to be non-negative */
|
|
|
|
PG_RETURN_FLOAT8(sqrt(Sxx / N));
|
|
}
|
|
|
|
Datum
|
|
float8_stddev_samp(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_stddev_samp", 3);
|
|
N = transvalues[0];
|
|
/* ignore Sx */
|
|
Sxx = transvalues[2];
|
|
|
|
/* Sample stddev is undefined when N is 0 or 1, so return NULL */
|
|
if (N <= 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx is guaranteed to be non-negative */
|
|
|
|
PG_RETURN_FLOAT8(sqrt(Sxx / (N - 1.0)));
|
|
}
|
|
|
|
/*
|
|
* =========================
|
|
* SQL2003 BINARY AGGREGATES
|
|
* =========================
|
|
*
|
|
* As with the preceding aggregates, we use the Youngs-Cramer algorithm to
|
|
* reduce rounding errors in the aggregate final functions.
|
|
*
|
|
* The transition datatype for all these aggregates is a 6-element array of
|
|
* float8, holding the values N, Sx=sum(X), Sxx=sum((X-Sx/N)^2), Sy=sum(Y),
|
|
* Syy=sum((Y-Sy/N)^2), Sxy=sum((X-Sx/N)*(Y-Sy/N)) in that order.
|
|
*
|
|
* Note that Y is the first argument to all these aggregates!
|
|
*
|
|
* It might seem attractive to optimize this by having multiple accumulator
|
|
* functions that only calculate the sums actually needed. But on most
|
|
* modern machines, a couple of extra floating-point multiplies will be
|
|
* insignificant compared to the other per-tuple overhead, so I've chosen
|
|
* to minimize code space instead.
|
|
*/
|
|
|
|
Datum
|
|
float8_regr_accum(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 newvalY = PG_GETARG_FLOAT8(1);
|
|
float8 newvalX = PG_GETARG_FLOAT8(2);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sx,
|
|
Sxx,
|
|
Sy,
|
|
Syy,
|
|
Sxy,
|
|
tmpX,
|
|
tmpY,
|
|
scale;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_accum", 6);
|
|
N = transvalues[0];
|
|
Sx = transvalues[1];
|
|
Sxx = transvalues[2];
|
|
Sy = transvalues[3];
|
|
Syy = transvalues[4];
|
|
Sxy = transvalues[5];
|
|
|
|
/*
|
|
* Use the Youngs-Cramer algorithm to incorporate the new values into the
|
|
* transition values.
|
|
*/
|
|
N += 1.0;
|
|
Sx += newvalX;
|
|
Sy += newvalY;
|
|
if (transvalues[0] > 0.0)
|
|
{
|
|
tmpX = newvalX * N - Sx;
|
|
tmpY = newvalY * N - Sy;
|
|
scale = 1.0 / (N * transvalues[0]);
|
|
Sxx += tmpX * tmpX * scale;
|
|
Syy += tmpY * tmpY * scale;
|
|
Sxy += tmpX * tmpY * scale;
|
|
|
|
/*
|
|
* Overflow check. We only report an overflow error when finite
|
|
* inputs lead to infinite results. Note also that Sxx, Syy and Sxy
|
|
* should be NaN if any of the relevant inputs are infinite, so we
|
|
* intentionally prevent them from becoming infinite.
|
|
*/
|
|
if (isinf(Sx) || isinf(Sxx) || isinf(Sy) || isinf(Syy) || isinf(Sxy))
|
|
{
|
|
if (((isinf(Sx) || isinf(Sxx)) &&
|
|
!isinf(transvalues[1]) && !isinf(newvalX)) ||
|
|
((isinf(Sy) || isinf(Syy)) &&
|
|
!isinf(transvalues[3]) && !isinf(newvalY)) ||
|
|
(isinf(Sxy) &&
|
|
!isinf(transvalues[1]) && !isinf(newvalX) &&
|
|
!isinf(transvalues[3]) && !isinf(newvalY)))
|
|
float_overflow_error();
|
|
|
|
if (isinf(Sxx))
|
|
Sxx = get_float8_nan();
|
|
if (isinf(Syy))
|
|
Syy = get_float8_nan();
|
|
if (isinf(Sxy))
|
|
Sxy = get_float8_nan();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* At the first input, we normally can leave Sxx et al as 0. However,
|
|
* if the first input is Inf or NaN, we'd better force the dependent
|
|
* sums to NaN; otherwise we will falsely report variance zero when
|
|
* there are no more inputs.
|
|
*/
|
|
if (isnan(newvalX) || isinf(newvalX))
|
|
Sxx = Sxy = get_float8_nan();
|
|
if (isnan(newvalY) || isinf(newvalY))
|
|
Syy = Sxy = get_float8_nan();
|
|
}
|
|
|
|
/*
|
|
* If we're invoked as an aggregate, we can cheat and modify our first
|
|
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
|
* new array with the updated transition data and return it.
|
|
*/
|
|
if (AggCheckCallContext(fcinfo, NULL))
|
|
{
|
|
transvalues[0] = N;
|
|
transvalues[1] = Sx;
|
|
transvalues[2] = Sxx;
|
|
transvalues[3] = Sy;
|
|
transvalues[4] = Syy;
|
|
transvalues[5] = Sxy;
|
|
|
|
PG_RETURN_ARRAYTYPE_P(transarray);
|
|
}
|
|
else
|
|
{
|
|
Datum transdatums[6];
|
|
ArrayType *result;
|
|
|
|
transdatums[0] = Float8GetDatumFast(N);
|
|
transdatums[1] = Float8GetDatumFast(Sx);
|
|
transdatums[2] = Float8GetDatumFast(Sxx);
|
|
transdatums[3] = Float8GetDatumFast(Sy);
|
|
transdatums[4] = Float8GetDatumFast(Syy);
|
|
transdatums[5] = Float8GetDatumFast(Sxy);
|
|
|
|
result = construct_array(transdatums, 6,
|
|
FLOAT8OID,
|
|
sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* float8_regr_combine
|
|
*
|
|
* An aggregate combine function used to combine two 6 fields
|
|
* aggregate transition data into a single transition data.
|
|
* This function is used only in two stage aggregation and
|
|
* shouldn't be called outside aggregate context.
|
|
*/
|
|
Datum
|
|
float8_regr_combine(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray1 = PG_GETARG_ARRAYTYPE_P(0);
|
|
ArrayType *transarray2 = PG_GETARG_ARRAYTYPE_P(1);
|
|
float8 *transvalues1;
|
|
float8 *transvalues2;
|
|
float8 N1,
|
|
Sx1,
|
|
Sxx1,
|
|
Sy1,
|
|
Syy1,
|
|
Sxy1,
|
|
N2,
|
|
Sx2,
|
|
Sxx2,
|
|
Sy2,
|
|
Syy2,
|
|
Sxy2,
|
|
tmp1,
|
|
tmp2,
|
|
N,
|
|
Sx,
|
|
Sxx,
|
|
Sy,
|
|
Syy,
|
|
Sxy;
|
|
|
|
transvalues1 = check_float8_array(transarray1, "float8_regr_combine", 6);
|
|
transvalues2 = check_float8_array(transarray2, "float8_regr_combine", 6);
|
|
|
|
N1 = transvalues1[0];
|
|
Sx1 = transvalues1[1];
|
|
Sxx1 = transvalues1[2];
|
|
Sy1 = transvalues1[3];
|
|
Syy1 = transvalues1[4];
|
|
Sxy1 = transvalues1[5];
|
|
|
|
N2 = transvalues2[0];
|
|
Sx2 = transvalues2[1];
|
|
Sxx2 = transvalues2[2];
|
|
Sy2 = transvalues2[3];
|
|
Syy2 = transvalues2[4];
|
|
Sxy2 = transvalues2[5];
|
|
|
|
/*--------------------
|
|
* The transition values combine using a generalization of the
|
|
* Youngs-Cramer algorithm as follows:
|
|
*
|
|
* N = N1 + N2
|
|
* Sx = Sx1 + Sx2
|
|
* Sxx = Sxx1 + Sxx2 + N1 * N2 * (Sx1/N1 - Sx2/N2)^2 / N
|
|
* Sy = Sy1 + Sy2
|
|
* Syy = Syy1 + Syy2 + N1 * N2 * (Sy1/N1 - Sy2/N2)^2 / N
|
|
* Sxy = Sxy1 + Sxy2 + N1 * N2 * (Sx1/N1 - Sx2/N2) * (Sy1/N1 - Sy2/N2) / N
|
|
*
|
|
* It's worth handling the special cases N1 = 0 and N2 = 0 separately
|
|
* since those cases are trivial, and we then don't need to worry about
|
|
* division-by-zero errors in the general case.
|
|
*--------------------
|
|
*/
|
|
if (N1 == 0.0)
|
|
{
|
|
N = N2;
|
|
Sx = Sx2;
|
|
Sxx = Sxx2;
|
|
Sy = Sy2;
|
|
Syy = Syy2;
|
|
Sxy = Sxy2;
|
|
}
|
|
else if (N2 == 0.0)
|
|
{
|
|
N = N1;
|
|
Sx = Sx1;
|
|
Sxx = Sxx1;
|
|
Sy = Sy1;
|
|
Syy = Syy1;
|
|
Sxy = Sxy1;
|
|
}
|
|
else
|
|
{
|
|
N = N1 + N2;
|
|
Sx = float8_pl(Sx1, Sx2);
|
|
tmp1 = Sx1 / N1 - Sx2 / N2;
|
|
Sxx = Sxx1 + Sxx2 + N1 * N2 * tmp1 * tmp1 / N;
|
|
if (unlikely(isinf(Sxx)) && !isinf(Sxx1) && !isinf(Sxx2))
|
|
float_overflow_error();
|
|
Sy = float8_pl(Sy1, Sy2);
|
|
tmp2 = Sy1 / N1 - Sy2 / N2;
|
|
Syy = Syy1 + Syy2 + N1 * N2 * tmp2 * tmp2 / N;
|
|
if (unlikely(isinf(Syy)) && !isinf(Syy1) && !isinf(Syy2))
|
|
float_overflow_error();
|
|
Sxy = Sxy1 + Sxy2 + N1 * N2 * tmp1 * tmp2 / N;
|
|
if (unlikely(isinf(Sxy)) && !isinf(Sxy1) && !isinf(Sxy2))
|
|
float_overflow_error();
|
|
}
|
|
|
|
/*
|
|
* If we're invoked as an aggregate, we can cheat and modify our first
|
|
* parameter in-place to reduce palloc overhead. Otherwise we construct a
|
|
* new array with the updated transition data and return it.
|
|
*/
|
|
if (AggCheckCallContext(fcinfo, NULL))
|
|
{
|
|
transvalues1[0] = N;
|
|
transvalues1[1] = Sx;
|
|
transvalues1[2] = Sxx;
|
|
transvalues1[3] = Sy;
|
|
transvalues1[4] = Syy;
|
|
transvalues1[5] = Sxy;
|
|
|
|
PG_RETURN_ARRAYTYPE_P(transarray1);
|
|
}
|
|
else
|
|
{
|
|
Datum transdatums[6];
|
|
ArrayType *result;
|
|
|
|
transdatums[0] = Float8GetDatumFast(N);
|
|
transdatums[1] = Float8GetDatumFast(Sx);
|
|
transdatums[2] = Float8GetDatumFast(Sxx);
|
|
transdatums[3] = Float8GetDatumFast(Sy);
|
|
transdatums[4] = Float8GetDatumFast(Syy);
|
|
transdatums[5] = Float8GetDatumFast(Sxy);
|
|
|
|
result = construct_array(transdatums, 6,
|
|
FLOAT8OID,
|
|
sizeof(float8), FLOAT8PASSBYVAL, TYPALIGN_DOUBLE);
|
|
|
|
PG_RETURN_ARRAYTYPE_P(result);
|
|
}
|
|
}
|
|
|
|
|
|
Datum
|
|
float8_regr_sxx(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_sxx", 6);
|
|
N = transvalues[0];
|
|
Sxx = transvalues[2];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx is guaranteed to be non-negative */
|
|
|
|
PG_RETURN_FLOAT8(Sxx);
|
|
}
|
|
|
|
Datum
|
|
float8_regr_syy(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Syy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_syy", 6);
|
|
N = transvalues[0];
|
|
Syy = transvalues[4];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Syy is guaranteed to be non-negative */
|
|
|
|
PG_RETURN_FLOAT8(Syy);
|
|
}
|
|
|
|
Datum
|
|
float8_regr_sxy(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_sxy", 6);
|
|
N = transvalues[0];
|
|
Sxy = transvalues[5];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* A negative result is valid here */
|
|
|
|
PG_RETURN_FLOAT8(Sxy);
|
|
}
|
|
|
|
Datum
|
|
float8_regr_avgx(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sx;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_avgx", 6);
|
|
N = transvalues[0];
|
|
Sx = transvalues[1];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8(Sx / N);
|
|
}
|
|
|
|
Datum
|
|
float8_regr_avgy(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_avgy", 6);
|
|
N = transvalues[0];
|
|
Sy = transvalues[3];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8(Sy / N);
|
|
}
|
|
|
|
Datum
|
|
float8_covar_pop(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_covar_pop", 6);
|
|
N = transvalues[0];
|
|
Sxy = transvalues[5];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8(Sxy / N);
|
|
}
|
|
|
|
Datum
|
|
float8_covar_samp(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_covar_samp", 6);
|
|
N = transvalues[0];
|
|
Sxy = transvalues[5];
|
|
|
|
/* if N is <= 1 we should return NULL */
|
|
if (N < 2.0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8(Sxy / (N - 1.0));
|
|
}
|
|
|
|
Datum
|
|
float8_corr(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx,
|
|
Syy,
|
|
Sxy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_corr", 6);
|
|
N = transvalues[0];
|
|
Sxx = transvalues[2];
|
|
Syy = transvalues[4];
|
|
Sxy = transvalues[5];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx and Syy are guaranteed to be non-negative */
|
|
|
|
/* per spec, return NULL for horizontal and vertical lines */
|
|
if (Sxx == 0 || Syy == 0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8(Sxy / sqrt(Sxx * Syy));
|
|
}
|
|
|
|
Datum
|
|
float8_regr_r2(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx,
|
|
Syy,
|
|
Sxy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_r2", 6);
|
|
N = transvalues[0];
|
|
Sxx = transvalues[2];
|
|
Syy = transvalues[4];
|
|
Sxy = transvalues[5];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx and Syy are guaranteed to be non-negative */
|
|
|
|
/* per spec, return NULL for a vertical line */
|
|
if (Sxx == 0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* per spec, return 1.0 for a horizontal line */
|
|
if (Syy == 0)
|
|
PG_RETURN_FLOAT8(1.0);
|
|
|
|
PG_RETURN_FLOAT8((Sxy * Sxy) / (Sxx * Syy));
|
|
}
|
|
|
|
Datum
|
|
float8_regr_slope(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sxx,
|
|
Sxy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_slope", 6);
|
|
N = transvalues[0];
|
|
Sxx = transvalues[2];
|
|
Sxy = transvalues[5];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx is guaranteed to be non-negative */
|
|
|
|
/* per spec, return NULL for a vertical line */
|
|
if (Sxx == 0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8(Sxy / Sxx);
|
|
}
|
|
|
|
Datum
|
|
float8_regr_intercept(PG_FUNCTION_ARGS)
|
|
{
|
|
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
|
|
float8 *transvalues;
|
|
float8 N,
|
|
Sx,
|
|
Sxx,
|
|
Sy,
|
|
Sxy;
|
|
|
|
transvalues = check_float8_array(transarray, "float8_regr_intercept", 6);
|
|
N = transvalues[0];
|
|
Sx = transvalues[1];
|
|
Sxx = transvalues[2];
|
|
Sy = transvalues[3];
|
|
Sxy = transvalues[5];
|
|
|
|
/* if N is 0 we should return NULL */
|
|
if (N < 1.0)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Note that Sxx is guaranteed to be non-negative */
|
|
|
|
/* per spec, return NULL for a vertical line */
|
|
if (Sxx == 0)
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_FLOAT8((Sy - Sx * Sxy / Sxx) / N);
|
|
}
|
|
|
|
|
|
/*
|
|
* ====================================
|
|
* MIXED-PRECISION ARITHMETIC OPERATORS
|
|
* ====================================
|
|
*/
|
|
|
|
/*
|
|
* float48pl - returns arg1 + arg2
|
|
* float48mi - returns arg1 - arg2
|
|
* float48mul - returns arg1 * arg2
|
|
* float48div - returns arg1 / arg2
|
|
*/
|
|
Datum
|
|
float48pl(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_pl((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48mi(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_mi((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48mul(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_mul((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48div(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_div((float8) arg1, arg2));
|
|
}
|
|
|
|
/*
|
|
* float84pl - returns arg1 + arg2
|
|
* float84mi - returns arg1 - arg2
|
|
* float84mul - returns arg1 * arg2
|
|
* float84div - returns arg1 / arg2
|
|
*/
|
|
Datum
|
|
float84pl(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_pl(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84mi(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_mi(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84mul(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_mul(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84div(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_FLOAT8(float8_div(arg1, (float8) arg2));
|
|
}
|
|
|
|
/*
|
|
* ====================
|
|
* COMPARISON OPERATORS
|
|
* ====================
|
|
*/
|
|
|
|
/*
|
|
* float48{eq,ne,lt,le,gt,ge} - float4/float8 comparison operations
|
|
*/
|
|
Datum
|
|
float48eq(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_eq((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48ne(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_ne((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48lt(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_lt((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48le(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_le((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48gt(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_gt((float8) arg1, arg2));
|
|
}
|
|
|
|
Datum
|
|
float48ge(PG_FUNCTION_ARGS)
|
|
{
|
|
float4 arg1 = PG_GETARG_FLOAT4(0);
|
|
float8 arg2 = PG_GETARG_FLOAT8(1);
|
|
|
|
PG_RETURN_BOOL(float8_ge((float8) arg1, arg2));
|
|
}
|
|
|
|
/*
|
|
* float84{eq,ne,lt,le,gt,ge} - float8/float4 comparison operations
|
|
*/
|
|
Datum
|
|
float84eq(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float8_eq(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84ne(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float8_ne(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84lt(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float8_lt(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84le(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float8_le(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84gt(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float8_gt(arg1, (float8) arg2));
|
|
}
|
|
|
|
Datum
|
|
float84ge(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 arg1 = PG_GETARG_FLOAT8(0);
|
|
float4 arg2 = PG_GETARG_FLOAT4(1);
|
|
|
|
PG_RETURN_BOOL(float8_ge(arg1, (float8) arg2));
|
|
}
|
|
|
|
/*
|
|
* Implements the float8 version of the width_bucket() function
|
|
* defined by SQL2003. See also width_bucket_numeric().
|
|
*
|
|
* 'bound1' and 'bound2' are the lower and upper bounds of the
|
|
* histogram's range, respectively. 'count' is the number of buckets
|
|
* in the histogram. width_bucket() returns an integer indicating the
|
|
* bucket number that 'operand' belongs to in an equiwidth histogram
|
|
* with the specified characteristics. An operand smaller than the
|
|
* lower bound is assigned to bucket 0. An operand greater than the
|
|
* upper bound is assigned to an additional bucket (with number
|
|
* count+1). We don't allow "NaN" for any of the float8 inputs, and we
|
|
* don't allow either of the histogram bounds to be +/- infinity.
|
|
*/
|
|
Datum
|
|
width_bucket_float8(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 operand = PG_GETARG_FLOAT8(0);
|
|
float8 bound1 = PG_GETARG_FLOAT8(1);
|
|
float8 bound2 = PG_GETARG_FLOAT8(2);
|
|
int32 count = PG_GETARG_INT32(3);
|
|
int32 result;
|
|
|
|
if (count <= 0.0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
|
|
errmsg("count must be greater than zero")));
|
|
|
|
if (isnan(operand) || isnan(bound1) || isnan(bound2))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
|
|
errmsg("operand, lower bound, and upper bound cannot be NaN")));
|
|
|
|
/* Note that we allow "operand" to be infinite */
|
|
if (isinf(bound1) || isinf(bound2))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
|
|
errmsg("lower and upper bounds must be finite")));
|
|
|
|
if (bound1 < bound2)
|
|
{
|
|
if (operand < bound1)
|
|
result = 0;
|
|
else if (operand >= bound2)
|
|
{
|
|
if (pg_add_s32_overflow(count, 1, &result))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("integer out of range")));
|
|
}
|
|
else
|
|
result = ((float8) count * (operand - bound1) / (bound2 - bound1)) + 1;
|
|
}
|
|
else if (bound1 > bound2)
|
|
{
|
|
if (operand > bound1)
|
|
result = 0;
|
|
else if (operand <= bound2)
|
|
{
|
|
if (pg_add_s32_overflow(count, 1, &result))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("integer out of range")));
|
|
}
|
|
else
|
|
result = ((float8) count * (bound1 - operand) / (bound1 - bound2)) + 1;
|
|
}
|
|
else
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
|
|
errmsg("lower bound cannot equal upper bound")));
|
|
result = 0; /* keep the compiler quiet */
|
|
}
|
|
|
|
PG_RETURN_INT32(result);
|
|
}
|