postgresql/src/backend/utils/adt/float.c

4178 lines
95 KiB
C

/*-------------------------------------------------------------------------
*
* float.c
* Functions for the built-in floating-point types.
*
* Portions Copyright (c) 1996-2023, 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/pg_prng.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 pg_prng_state drandom_seed;
/* 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);
PG_RETURN_FLOAT4(float4in_internal(num, NULL, "real", num,
fcinfo->context));
}
/*
* float4in_internal - guts of float4in()
*
* This is exposed for use by functions that want a reasonably
* platform-independent way of inputting floats. The behavior is
* essentially like strtof + ereturn on error.
*
* Uses the same API as float8in_internal below, so most of its
* comments also apply here, except regarding use in geometric types.
*/
float4
float4in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
struct Node *escontext)
{
float val;
char *endptr;
/*
* endptr points to the first character _after_ the sequence we recognized
* as a valid floating point number. orig_string points to the original
* input string.
*/
/* 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')
ereturn(escontext, 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
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.
*/
if (val == 0.0 ||
#if !defined(HUGE_VALF)
isinf(val)
#else
(val >= HUGE_VALF || val <= -HUGE_VALF)
#endif
)
{
/* see comments in float8in_internal for rationale */
char *errnumber = pstrdup(num);
errnumber[endptr - num] = '\0';
ereturn(escontext, 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type real",
errnumber)));
}
}
else
ereturn(escontext, 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
}
/* 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')
ereturn(escontext, 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
return 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,
fcinfo->context));
}
/*
* float8in_internal - 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 + ereturn on error, but note the following
* differences:
* 1. Both leading and trailing whitespace are skipped.
* 2. If endptr_p is NULL, we report 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.
*
* If escontext points to an ErrorSaveContext node, that is filled instead
* of throwing an error; the caller must check SOFT_ERROR_OCCURRED()
* to detect errors.
*
* "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.
*/
float8
float8in_internal(char *num, char **endptr_p,
const char *type_name, const char *orig_string,
struct Node *escontext)
{
double val;
char *endptr;
/* 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')
ereturn(escontext, 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
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';
ereturn(escontext, 0,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("\"%s\" is out of range for type double precision",
errnumber)));
}
}
else
ereturn(escontext, 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
}
/* 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')
ereturn(escontext, 0,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
type_name, orig_string)));
return val;
}
/*
* 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(fabsf(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 cases where both base and offset are infinite, and computing
* base +/- offset would produce NaN. This corresponds to a window frame
* whose boundary infinitely precedes +inf or infinitely follows -inf,
* which is not well-defined. For consistency with other cases involving
* infinities, such as the fact that +inf infinitely follows +inf, we
* choose to assume that +inf infinitely precedes +inf and -inf infinitely
* follows -inf, and therefore that all finite and infinite values are in
* such a window frame.
*
* offset is known positive, so we need only check the sign of base in
* this test.
*/
if (isinf(offset) && isinf(base) &&
(sub ? base > 0 : base < 0))
PG_RETURN_BOOL(true);
/*
* Otherwise it should be safe to compute base +/- offset. We trust the
* FPU to cope if an input 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 cases where both base and offset are infinite, and computing
* base +/- offset would produce NaN. This corresponds to a window frame
* whose boundary infinitely precedes +inf or infinitely follows -inf,
* which is not well-defined. For consistency with other cases involving
* infinities, such as the fact that +inf infinitely follows +inf, we
* choose to assume that +inf infinitely precedes +inf and -inf infinitely
* follows -inf, and therefore that all finite and infinite values are in
* such a window frame.
*
* offset is known positive, so we need only check the sign of base in
* this test.
*/
if (isinf(offset) && isinf(base) &&
(sub ? base > 0 : base < 0))
PG_RETURN_BOOL(true);
/*
* Otherwise it should be safe to compute base +/- offset. We trust the
* FPU to cope if an input 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")));
/*
* We don't trust the platform's pow() to handle infinity cases per POSIX
* spec either, so deal with those explicitly too. It's easier to handle
* infinite y first, so that it doesn't matter if x is also infinite.
*/
if (isinf(arg2))
{
float8 absx = fabs(arg1);
if (absx == 1.0)
result = 1.0;
else if (arg2 > 0.0) /* y = +Inf */
{
if (absx > 1.0)
result = arg2;
else
result = 0.0;
}
else /* y = -Inf */
{
if (absx > 1.0)
result = 0.0;
else
result = -arg2;
}
}
else if (isinf(arg1))
{
if (arg2 == 0.0)
result = 1.0;
else if (arg1 > 0.0) /* x = +Inf */
{
if (arg2 > 0.0)
result = arg1;
else
result = 0.0;
}
else /* x = -Inf */
{
/*
* Per POSIX, the sign of the result depends on whether y is an
* odd integer. Since x < 0, we already know from the previous
* domain check that y is an integer. It is odd if y/2 is not
* also an integer.
*/
float8 halfy = arg2 / 2; /* should be computed exactly */
bool yisoddinteger = (floor(halfy) != halfy);
if (arg2 > 0.0)
result = yisoddinteger ? arg1 : -arg1;
else
result = yisoddinteger ? -0.0 : 0.0;
}
}
else
{
/*
* pow() sets errno on only some platforms, depending on whether it
* follows _IEEE_, _POSIX_, _XOPEN_, or _SVID_, so we must check both
* errno and invalid output values. (We can't rely on just the
* latter, either; some old platforms return a large-but-finite
* HUGE_VAL when reporting overflow.)
*/
errno = 0;
result = pow(arg1, arg2);
if (errno == EDOM || isnan(result))
{
/*
* We handled all possible domain errors above, so this should be
* impossible. However, old glibc versions on x86 have a bug that
* causes them to fail this way for abs(y) greater than 2^63:
*
* https://sourceware.org/bugzilla/show_bug.cgi?id=3866
*
* Hence, if we get here, assume y is finite but large (large
* enough to be certainly even). The result should be 0 if x == 0,
* 1.0 if abs(x) == 1.0, otherwise an overflow or underflow error.
*/
if (arg1 == 0.0)
result = 0.0; /* we already verified y is positive */
else
{
float8 absx = fabs(arg1);
if (absx == 1.0)
result = 1.0;
else if (arg2 >= 0.0 ? (absx > 1.0) : (absx < 1.0))
float_overflow_error();
else
float_underflow_error();
}
}
else if (errno == ERANGE)
{
if (result != 0.0)
float_overflow_error();
else
float_underflow_error();
}
else
{
if (unlikely(isinf(result)))
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;
/*
* Handle NaN and Inf cases explicitly. This avoids needing to assume
* that the platform's exp() conforms to POSIX for these cases, and it
* removes some edge cases for the overflow checks below.
*/
if (isnan(arg1))
result = arg1;
else if (isinf(arg1))
{
/* Per POSIX, exp(-Inf) is 0 */
result = (arg1 > 0.0) ? arg1 : 0;
}
else
{
/*
* On some platforms, exp() will not set errno but just return Inf or
* zero to report overflow/underflow; therefore, test both cases.
*/
errno = 0;
result = exp(arg1);
if (unlikely(errno == ERANGE))
{
if (result != 0.0)
float_overflow_error();
else
float_underflow_error();
}
else if (unlikely(isinf(result)))
float_overflow_error();
else 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);
}
/* ========== ERROR FUNCTIONS ========== */
/*
* derf - returns the error function: erf(arg1)
*/
Datum
derf(PG_FUNCTION_ARGS)
{
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 result;
/*
* For erf, we don't need an errno check because it never overflows.
*/
result = erf(arg1);
if (unlikely(isinf(result)))
float_overflow_error();
PG_RETURN_FLOAT8(result);
}
/*
* derfc - returns the complementary error function: 1 - erf(arg1)
*/
Datum
derfc(PG_FUNCTION_ARGS)
{
float8 arg1 = PG_GETARG_FLOAT8(0);
float8 result;
/*
* For erfc, we don't need an errno check because it never overflows.
*/
result = erfc(arg1);
if (unlikely(isinf(result)))
float_overflow_error();
PG_RETURN_FLOAT8(result);
}
/* ========== RANDOM FUNCTIONS ========== */
/*
* initialize_drandom_seed - initialize drandom_seed if not yet done
*/
static void
initialize_drandom_seed(void)
{
/* 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 (unlikely(!pg_prng_strong_seed(&drandom_seed)))
{
TimestampTz now = GetCurrentTimestamp();
uint64 iseed;
/* Mix the PID with the most predictable bits of the timestamp */
iseed = (uint64) now ^ ((uint64) MyProcPid << 32);
pg_prng_seed(&drandom_seed, iseed);
}
drandom_seed_set = true;
}
}
/*
* drandom - returns a random number
*/
Datum
drandom(PG_FUNCTION_ARGS)
{
float8 result;
initialize_drandom_seed();
/* pg_prng_double produces desired result range [0.0 - 1.0) */
result = pg_prng_double(&drandom_seed);
PG_RETURN_FLOAT8(result);
}
/*
* drandom_normal - returns a random number from a normal distribution
*/
Datum
drandom_normal(PG_FUNCTION_ARGS)
{
float8 mean = PG_GETARG_FLOAT8(0);
float8 stddev = PG_GETARG_FLOAT8(1);
float8 result,
z;
initialize_drandom_seed();
/* Get random value from standard normal(mean = 0.0, stddev = 1.0) */
z = pg_prng_double_normal(&drandom_seed);
/* Transform the normal standard variable (z) */
/* using the target normal distribution parameters */
result = (stddev * z) + mean;
PG_RETURN_FLOAT8(result);
}
/*
* setseed - set seed for the random number generator
*/
Datum
setseed(PG_FUNCTION_ARGS)
{
float8 seed = PG_GETARG_FLOAT8(0);
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)));
pg_prng_fseed(&drandom_seed, seed);
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)
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
{
if (!isinf(bound2 - bound1))
{
/* The quotient is surely in [0,1], so this can't overflow */
result = count * ((operand - bound1) / (bound2 - bound1));
}
else
{
/*
* We get here if bound2 - bound1 overflows DBL_MAX. Since
* both bounds are finite, their difference can't exceed twice
* DBL_MAX; so we can perform the computation without overflow
* by dividing all the inputs by 2. That should be exact too,
* except in the case where a very small operand underflows to
* zero, which would have negligible impact on the result
* given such large bounds.
*/
result = count * ((operand / 2 - bound1 / 2) / (bound2 / 2 - bound1 / 2));
}
/* The quotient could round to 1.0, which would be a lie */
if (result >= count)
result = count - 1;
/* Having done that, we can add 1 without fear of overflow */
result++;
}
}
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
{
if (!isinf(bound1 - bound2))
result = count * ((bound1 - operand) / (bound1 - bound2));
else
result = count * ((bound1 / 2 - operand / 2) / (bound1 / 2 - bound2 / 2));
if (result >= count)
result = count - 1;
result++;
}
}
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);
}