Commit a57d312a authored by Tom Lane's avatar Tom Lane

Support infinity and -infinity in the numeric data type.

Add infinities that behave the same as they do in the floating-point
data types.  Aside from any intrinsic usefulness these may have,
this closes an important gap in our ability to convert floating
values to numeric and/or replace float-based APIs with numeric.

The new values are represented by bit patterns that were formerly
not used (although old code probably would take them for NaNs).
So there shouldn't be any pg_upgrade hazard.

Patch by me, reviewed by Dean Rasheed and Andrew Gierth

Discussion: https://postgr.es/m/606717.1591924582@sss.pgh.pa.us
parent 9e108984
...@@ -227,10 +227,8 @@ SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem) ...@@ -227,10 +227,8 @@ SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem)
/* /*
* jsonb doesn't allow infinity or NaN (per JSON * jsonb doesn't allow infinity or NaN (per JSON
* specification), but the numeric type that is used for the * specification), but the numeric type that is used for the
* storage accepts NaN, so we have to prevent it here * storage accepts those, so we have to reject them here
* explicitly. We don't really have to check for isinf() * explicitly.
* here, as numeric doesn't allow it and it would be caught
* later, but it makes for a nicer error message.
*/ */
if (isinf(nval)) if (isinf(nval))
ereport(ERROR, ereport(ERROR,
......
...@@ -387,14 +387,17 @@ PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum) ...@@ -387,14 +387,17 @@ PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum)
pfree(str); pfree(str);
/* /*
* jsonb doesn't allow NaN (per JSON specification), so we have to prevent * jsonb doesn't allow NaN or infinity (per JSON specification), so we
* it here explicitly. (Infinity is also not allowed in jsonb, but * have to reject those here explicitly.
* numeric_in above already catches that.)
*/ */
if (numeric_is_nan(num)) if (numeric_is_nan(num))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("cannot convert NaN to jsonb"))); errmsg("cannot convert NaN to jsonb")));
if (numeric_is_inf(num))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("cannot convert infinity to jsonb")));
jbvNum->type = jbvNumeric; jbvNum->type = jbvNumeric;
jbvNum->val.numeric = num; jbvNum->val.numeric = num;
......
...@@ -554,9 +554,9 @@ NUMERIC(<replaceable>precision</replaceable>) ...@@ -554,9 +554,9 @@ NUMERIC(<replaceable>precision</replaceable>)
<programlisting> <programlisting>
NUMERIC NUMERIC
</programlisting> </programlisting>
without any precision or scale creates a column in which numeric without any precision or scale creates an <quote>unconstrained
values of any precision and scale can be stored, up to the numeric</quote> column in which numeric values of any length can be
implementation limit on precision. A column of this kind will stored, up to the implementation limits. A column of this kind will
not coerce input values to any particular scale, whereas not coerce input values to any particular scale, whereas
<type>numeric</type> columns with a declared scale will coerce <type>numeric</type> columns with a declared scale will coerce
input values to that scale. (The <acronym>SQL</acronym> standard input values to that scale. (The <acronym>SQL</acronym> standard
...@@ -568,10 +568,10 @@ NUMERIC ...@@ -568,10 +568,10 @@ NUMERIC
<note> <note>
<para> <para>
The maximum allowed precision when explicitly specified in the The maximum precision that can be explicitly specified in
type declaration is 1000; <type>NUMERIC</type> without a specified a <type>NUMERIC</type> type declaration is 1000. An
precision is subject to the limits described in <xref unconstrained <type>NUMERIC</type> column is subject to the limits
linkend="datatype-numeric-table"/>. described in <xref linkend="datatype-numeric-table"/>.
</para> </para>
</note> </note>
...@@ -593,6 +593,11 @@ NUMERIC ...@@ -593,6 +593,11 @@ NUMERIC
plus three to eight bytes overhead. plus three to eight bytes overhead.
</para> </para>
<indexterm>
<primary>infinity</primary>
<secondary>numeric (data type)</secondary>
</indexterm>
<indexterm> <indexterm>
<primary>NaN</primary> <primary>NaN</primary>
<see>not a number</see> <see>not a number</see>
...@@ -604,13 +609,44 @@ NUMERIC ...@@ -604,13 +609,44 @@ NUMERIC
</indexterm> </indexterm>
<para> <para>
In addition to ordinary numeric values, the <type>numeric</type> In addition to ordinary numeric values, the <type>numeric</type> type
type allows the special value <literal>NaN</literal>, meaning has several special values:
<quote>not-a-number</quote>. Any operation on <literal>NaN</literal> <literallayout>
yields another <literal>NaN</literal>. When writing this value <literal>Infinity</literal>
as a constant in an SQL command, you must put quotes around it, <literal>-Infinity</literal>
for example <literal>UPDATE table SET x = 'NaN'</literal>. On input, <literal>NaN</literal>
the string <literal>NaN</literal> is recognized in a case-insensitive manner. </literallayout>
These are adapted from the IEEE 754 standard, and represent
<quote>infinity</quote>, <quote>negative infinity</quote>, and
<quote>not-a-number</quote>, respectively. When writing these values
as constants in an SQL command, you must put quotes around them,
for example <literal>UPDATE table SET x = '-Infinity'</literal>.
On input, these strings are recognized in a case-insensitive manner.
The infinity values can alternatively be spelled <literal>inf</literal>
and <literal>-inf</literal>.
</para>
<para>
The infinity values behave as per mathematical expectations. For
example, <literal>Infinity</literal> plus any finite value equals
<literal>Infinity</literal>, as does <literal>Infinity</literal>
plus <literal>Infinity</literal>; but <literal>Infinity</literal>
minus <literal>Infinity</literal> yields <literal>NaN</literal> (not a
number), because it has no well-defined interpretation. Note that an
infinity can only be stored in an unconstrained <type>numeric</type>
column, because it notionally exceeds any finite precision limit.
</para>
<para>
The <literal>NaN</literal> (not a number) value is used to represent
undefined calculational results. In general, any operation with
a <literal>NaN</literal> input yields another <literal>NaN</literal>.
The only exception is when the operation's other inputs are such that
the same output would be obtained if the <literal>NaN</literal> were to
be replaced by any finite or infinite numeric value; then, that output
value is used for <literal>NaN</literal> too. (An example of this
principle is that <literal>NaN</literal> raised to the zero power
yields one.)
</para> </para>
<note> <note>
...@@ -781,9 +817,14 @@ FROM generate_series(-3.5, 3.5, 1) as x; ...@@ -781,9 +817,14 @@ FROM generate_series(-3.5, 3.5, 1) as x;
</para> </para>
</note> </note>
<indexterm>
<primary>infinity</primary>
<secondary>floating point</secondary>
</indexterm>
<indexterm> <indexterm>
<primary>not a number</primary> <primary>not a number</primary>
<secondary>double precision</secondary> <secondary>floating point</secondary>
</indexterm> </indexterm>
<para> <para>
...@@ -800,11 +841,13 @@ FROM generate_series(-3.5, 3.5, 1) as x; ...@@ -800,11 +841,13 @@ FROM generate_series(-3.5, 3.5, 1) as x;
as constants in an SQL command, you must put quotes around them, as constants in an SQL command, you must put quotes around them,
for example <literal>UPDATE table SET x = '-Infinity'</literal>. On input, for example <literal>UPDATE table SET x = '-Infinity'</literal>. On input,
these strings are recognized in a case-insensitive manner. these strings are recognized in a case-insensitive manner.
The infinity values can alternatively be spelled <literal>inf</literal>
and <literal>-inf</literal>.
</para> </para>
<note> <note>
<para> <para>
IEEE754 specifies that <literal>NaN</literal> should not compare equal IEEE 754 specifies that <literal>NaN</literal> should not compare equal
to any other floating-point value (including <literal>NaN</literal>). to any other floating-point value (including <literal>NaN</literal>).
In order to allow floating-point values to be sorted and used In order to allow floating-point values to be sorted and used
in tree-based indexes, <productname>PostgreSQL</productname> treats in tree-based indexes, <productname>PostgreSQL</productname> treats
......
...@@ -6129,9 +6129,12 @@ numeric_to_char(PG_FUNCTION_ARGS) ...@@ -6129,9 +6129,12 @@ numeric_to_char(PG_FUNCTION_ARGS)
/* /*
* numeric_out_sci() does not emit a sign for positive numbers. We * numeric_out_sci() does not emit a sign for positive numbers. We
* need to add a space in this case so that positive and negative * need to add a space in this case so that positive and negative
* numbers are aligned. We also have to do the right thing for NaN. * numbers are aligned. Also must check for NaN/infinity cases, which
* we handle the same way as in float8_to_char.
*/ */
if (strcmp(orgnum, "NaN") == 0) if (strcmp(orgnum, "NaN") == 0 ||
strcmp(orgnum, "Infinity") == 0 ||
strcmp(orgnum, "-Infinity") == 0)
{ {
/* /*
* Allow 6 characters for the leading sign, the decimal point, * Allow 6 characters for the leading sign, the decimal point,
...@@ -6346,7 +6349,7 @@ int8_to_char(PG_FUNCTION_ARGS) ...@@ -6346,7 +6349,7 @@ int8_to_char(PG_FUNCTION_ARGS)
/* /*
* numeric_out_sci() does not emit a sign for positive numbers. We * numeric_out_sci() does not emit a sign for positive numbers. We
* need to add a space in this case so that positive and negative * need to add a space in this case so that positive and negative
* numbers are aligned. We don't have to worry about NaN here. * numbers are aligned. We don't have to worry about NaN/inf here.
*/ */
if (*orgnum != '-') if (*orgnum != '-')
{ {
......
...@@ -109,14 +109,13 @@ typedef int16 NumericDigit; ...@@ -109,14 +109,13 @@ typedef int16 NumericDigit;
* If the high bits of the first word of a NumericChoice (n_header, or * If the high bits of the first word of a NumericChoice (n_header, or
* n_short.n_header, or n_long.n_sign_dscale) are NUMERIC_SHORT, then the * n_short.n_header, or n_long.n_sign_dscale) are NUMERIC_SHORT, then the
* numeric follows the NumericShort format; if they are NUMERIC_POS or * numeric follows the NumericShort format; if they are NUMERIC_POS or
* NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_NAN, * NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_SPECIAL,
* it is a NaN. We currently always store a NaN using just two bytes (i.e. * the value is a NaN or Infinity. We currently always store SPECIAL values
* only n_header), but previous releases used only the NumericLong format, * using just two bytes (i.e. only n_header), but previous releases used only
* so we might find 4-byte NaNs on disk if a database has been migrated using * the NumericLong format, so we might find 4-byte NaNs (though not infinities)
* pg_upgrade. In either case, when the high bits indicate a NaN, the * on disk if a database has been migrated using pg_upgrade. In either case,
* remaining bits are never examined. Currently, we always initialize these * the low-order bits of a special value's header are reserved and currently
* to zero, but it might be possible to use them for some other purpose in * should always be set to zero.
* the future.
* *
* In the NumericShort format, the remaining 14 bits of the header word * In the NumericShort format, the remaining 14 bits of the header word
* (n_short.n_header) are allocated as follows: 1 for sign (positive or * (n_short.n_header) are allocated as follows: 1 for sign (positive or
...@@ -168,25 +167,47 @@ struct NumericData ...@@ -168,25 +167,47 @@ struct NumericData
#define NUMERIC_POS 0x0000 #define NUMERIC_POS 0x0000
#define NUMERIC_NEG 0x4000 #define NUMERIC_NEG 0x4000
#define NUMERIC_SHORT 0x8000 #define NUMERIC_SHORT 0x8000
#define NUMERIC_NAN 0xC000 #define NUMERIC_SPECIAL 0xC000
#define NUMERIC_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_SIGN_MASK) #define NUMERIC_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_SIGN_MASK)
#define NUMERIC_IS_NAN(n) (NUMERIC_FLAGBITS(n) == NUMERIC_NAN)
#define NUMERIC_IS_SHORT(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SHORT) #define NUMERIC_IS_SHORT(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SHORT)
#define NUMERIC_IS_SPECIAL(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SPECIAL)
#define NUMERIC_HDRSZ (VARHDRSZ + sizeof(uint16) + sizeof(int16)) #define NUMERIC_HDRSZ (VARHDRSZ + sizeof(uint16) + sizeof(int16))
#define NUMERIC_HDRSZ_SHORT (VARHDRSZ + sizeof(uint16)) #define NUMERIC_HDRSZ_SHORT (VARHDRSZ + sizeof(uint16))
/* /*
* If the flag bits are NUMERIC_SHORT or NUMERIC_NAN, we want the short header; * If the flag bits are NUMERIC_SHORT or NUMERIC_SPECIAL, we want the short
* otherwise, we want the long one. Instead of testing against each value, we * header; otherwise, we want the long one. Instead of testing against each
* can just look at the high bit, for a slight efficiency gain. * value, we can just look at the high bit, for a slight efficiency gain.
*/ */
#define NUMERIC_HEADER_IS_SHORT(n) (((n)->choice.n_header & 0x8000) != 0) #define NUMERIC_HEADER_IS_SHORT(n) (((n)->choice.n_header & 0x8000) != 0)
#define NUMERIC_HEADER_SIZE(n) \ #define NUMERIC_HEADER_SIZE(n) \
(VARHDRSZ + sizeof(uint16) + \ (VARHDRSZ + sizeof(uint16) + \
(NUMERIC_HEADER_IS_SHORT(n) ? 0 : sizeof(int16))) (NUMERIC_HEADER_IS_SHORT(n) ? 0 : sizeof(int16)))
/*
* Definitions for special values (NaN, positive infinity, negative infinity).
*
* The two bits after the NUMERIC_SPECIAL bits are 00 for NaN, 01 for positive
* infinity, 11 for negative infinity. (This makes the sign bit match where
* it is in a short-format value, though we make no use of that at present.)
* We could mask off the remaining bits before testing the active bits, but
* currently those bits must be zeroes, so masking would just add cycles.
*/
#define NUMERIC_EXT_SIGN_MASK 0xF000 /* high bits plus NaN/Inf flag bits */
#define NUMERIC_NAN 0xC000
#define NUMERIC_PINF 0xD000
#define NUMERIC_NINF 0xF000
#define NUMERIC_INF_SIGN_MASK 0x2000
#define NUMERIC_EXT_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_EXT_SIGN_MASK)
#define NUMERIC_IS_NAN(n) ((n)->choice.n_header == NUMERIC_NAN)
#define NUMERIC_IS_PINF(n) ((n)->choice.n_header == NUMERIC_PINF)
#define NUMERIC_IS_NINF(n) ((n)->choice.n_header == NUMERIC_NINF)
#define NUMERIC_IS_INF(n) \
(((n)->choice.n_header & ~NUMERIC_INF_SIGN_MASK) == NUMERIC_PINF)
/* /*
* Short format definitions. * Short format definitions.
*/ */
...@@ -202,7 +223,13 @@ struct NumericData ...@@ -202,7 +223,13 @@ struct NumericData
#define NUMERIC_SHORT_WEIGHT_MIN (-(NUMERIC_SHORT_WEIGHT_MASK+1)) #define NUMERIC_SHORT_WEIGHT_MIN (-(NUMERIC_SHORT_WEIGHT_MASK+1))
/* /*
* Extract sign, display scale, weight. * Extract sign, display scale, weight. These macros extract field values
* suitable for the NumericVar format from the Numeric (on-disk) format.
*
* Note that we don't trouble to ensure that dscale and weight read as zero
* for an infinity; however, that doesn't matter since we never convert
* "special" numerics to NumericVar form. Only the constants defined below
* (const_nan, etc) ever represent a non-finite value as a NumericVar.
*/ */
#define NUMERIC_DSCALE_MASK 0x3FFF #define NUMERIC_DSCALE_MASK 0x3FFF
...@@ -210,7 +237,9 @@ struct NumericData ...@@ -210,7 +237,9 @@ struct NumericData
#define NUMERIC_SIGN(n) \ #define NUMERIC_SIGN(n) \
(NUMERIC_IS_SHORT(n) ? \ (NUMERIC_IS_SHORT(n) ? \
(((n)->choice.n_short.n_header & NUMERIC_SHORT_SIGN_MASK) ? \ (((n)->choice.n_short.n_header & NUMERIC_SHORT_SIGN_MASK) ? \
NUMERIC_NEG : NUMERIC_POS) : NUMERIC_FLAGBITS(n)) NUMERIC_NEG : NUMERIC_POS) : \
(NUMERIC_IS_SPECIAL(n) ? \
NUMERIC_EXT_FLAGBITS(n) : NUMERIC_FLAGBITS(n)))
#define NUMERIC_DSCALE(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \ #define NUMERIC_DSCALE(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \
((n)->choice.n_short.n_header & NUMERIC_SHORT_DSCALE_MASK) \ ((n)->choice.n_short.n_header & NUMERIC_SHORT_DSCALE_MASK) \
>> NUMERIC_SHORT_DSCALE_SHIFT \ >> NUMERIC_SHORT_DSCALE_SHIFT \
...@@ -227,7 +256,9 @@ struct NumericData ...@@ -227,7 +256,9 @@ struct NumericData
* complex. * complex.
* *
* The value represented by a NumericVar is determined by the sign, weight, * The value represented by a NumericVar is determined by the sign, weight,
* ndigits, and digits[] array. * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
* then only the sign field matters; ndigits should be zero, and the weight
* and dscale fields are ignored.
* *
* Note: the first digit of a NumericVar's value is assumed to be multiplied * Note: the first digit of a NumericVar's value is assumed to be multiplied
* by NBASE ** weight. Another way to say it is that there are weight+1 * by NBASE ** weight. Another way to say it is that there are weight+1
...@@ -274,7 +305,7 @@ typedef struct NumericVar ...@@ -274,7 +305,7 @@ typedef struct NumericVar
{ {
int ndigits; /* # of digits in digits[] - can be 0! */ int ndigits; /* # of digits in digits[] - can be 0! */
int weight; /* weight of first digit */ int weight; /* weight of first digit */
int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */ int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
int dscale; /* display scale */ int dscale; /* display scale */
NumericDigit *buf; /* start of palloc'd space for digits[] */ NumericDigit *buf; /* start of palloc'd space for digits[] */
NumericDigit *digits; /* base-NBASE digits */ NumericDigit *digits; /* base-NBASE digits */
...@@ -354,16 +385,26 @@ typedef struct NumericSumAccum ...@@ -354,16 +385,26 @@ typedef struct NumericSumAccum
* representations for numeric values in order to avoid depending on * representations for numeric values in order to avoid depending on
* USE_FLOAT8_BYVAL. The type of abbreviation we use is based only on * USE_FLOAT8_BYVAL. The type of abbreviation we use is based only on
* the size of a datum, not the argument-passing convention for float8. * the size of a datum, not the argument-passing convention for float8.
*
* The range of abbreviations for finite values is from +PG_INT64/32_MAX
* to -PG_INT64/32_MAX. NaN has the abbreviation PG_INT64/32_MIN, and we
* define the sort ordering to make that work out properly (see further
* comments below). PINF and NINF share the abbreviations of the largest
* and smallest finite abbreviation classes.
*/ */
#define NUMERIC_ABBREV_BITS (SIZEOF_DATUM * BITS_PER_BYTE) #define NUMERIC_ABBREV_BITS (SIZEOF_DATUM * BITS_PER_BYTE)
#if SIZEOF_DATUM == 8 #if SIZEOF_DATUM == 8
#define NumericAbbrevGetDatum(X) ((Datum) (X)) #define NumericAbbrevGetDatum(X) ((Datum) (X))
#define DatumGetNumericAbbrev(X) ((int64) (X)) #define DatumGetNumericAbbrev(X) ((int64) (X))
#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT64_MIN) #define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT64_MIN)
#define NUMERIC_ABBREV_PINF NumericAbbrevGetDatum(-PG_INT64_MAX)
#define NUMERIC_ABBREV_NINF NumericAbbrevGetDatum(PG_INT64_MAX)
#else #else
#define NumericAbbrevGetDatum(X) ((Datum) (X)) #define NumericAbbrevGetDatum(X) ((Datum) (X))
#define DatumGetNumericAbbrev(X) ((int32) (X)) #define DatumGetNumericAbbrev(X) ((int32) (X))
#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT32_MIN) #define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT32_MIN)
#define NUMERIC_ABBREV_PINF NumericAbbrevGetDatum(-PG_INT32_MAX)
#define NUMERIC_ABBREV_NINF NumericAbbrevGetDatum(PG_INT32_MAX)
#endif #endif
...@@ -379,6 +420,9 @@ static const NumericDigit const_one_data[1] = {1}; ...@@ -379,6 +420,9 @@ static const NumericDigit const_one_data[1] = {1};
static const NumericVar const_one = static const NumericVar const_one =
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data}; {1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data};
static const NumericVar const_minus_one =
{1, 0, NUMERIC_NEG, 0, NULL, (NumericDigit *) const_one_data};
static const NumericDigit const_two_data[1] = {2}; static const NumericDigit const_two_data[1] = {2};
static const NumericVar const_two = static const NumericVar const_two =
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data}; {1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
...@@ -416,6 +460,12 @@ static const NumericVar const_one_point_one = ...@@ -416,6 +460,12 @@ static const NumericVar const_one_point_one =
static const NumericVar const_nan = static const NumericVar const_nan =
{0, 0, NUMERIC_NAN, 0, NULL, NULL}; {0, 0, NUMERIC_NAN, 0, NULL, NULL};
static const NumericVar const_pinf =
{0, 0, NUMERIC_PINF, 0, NULL, NULL};
static const NumericVar const_ninf =
{0, 0, NUMERIC_NINF, 0, NULL, NULL};
#if DEC_DIGITS == 4 #if DEC_DIGITS == 4
static const int round_powers[4] = {0, 1000, 100, 10}; static const int round_powers[4] = {0, 1000, 100, 10};
#endif #endif
...@@ -465,10 +515,12 @@ static void set_var_from_var(const NumericVar *value, NumericVar *dest); ...@@ -465,10 +515,12 @@ static void set_var_from_var(const NumericVar *value, NumericVar *dest);
static char *get_str_from_var(const NumericVar *var); static char *get_str_from_var(const NumericVar *var);
static char *get_str_from_var_sci(const NumericVar *var, int rscale); static char *get_str_from_var_sci(const NumericVar *var, int rscale);
static Numeric duplicate_numeric(Numeric num);
static Numeric make_result(const NumericVar *var); static Numeric make_result(const NumericVar *var);
static Numeric make_result_opt_error(const NumericVar *var, bool *error); static Numeric make_result_opt_error(const NumericVar *var, bool *error);
static void apply_typmod(NumericVar *var, int32 typmod); static void apply_typmod(NumericVar *var, int32 typmod);
static void apply_typmod_special(Numeric num, int32 typmod);
static bool numericvar_to_int32(const NumericVar *var, int32 *result); static bool numericvar_to_int32(const NumericVar *var, int32 *result);
static bool numericvar_to_int64(const NumericVar *var, int64 *result); static bool numericvar_to_int64(const NumericVar *var, int64 *result);
...@@ -478,7 +530,6 @@ static bool numericvar_to_uint64(const NumericVar *var, uint64 *result); ...@@ -478,7 +530,6 @@ static bool numericvar_to_uint64(const NumericVar *var, uint64 *result);
static bool numericvar_to_int128(const NumericVar *var, int128 *result); static bool numericvar_to_int128(const NumericVar *var, int128 *result);
static void int128_to_numericvar(int128 val, NumericVar *var); static void int128_to_numericvar(int128 val, NumericVar *var);
#endif #endif
static double numeric_to_double_no_overflow(Numeric num);
static double numericvar_to_double_no_overflow(const NumericVar *var); static double numericvar_to_double_no_overflow(const NumericVar *var);
static Datum numeric_abbrev_convert(Datum original_datum, SortSupport ssup); static Datum numeric_abbrev_convert(Datum original_datum, SortSupport ssup);
...@@ -587,23 +638,43 @@ numeric_in(PG_FUNCTION_ARGS) ...@@ -587,23 +638,43 @@ numeric_in(PG_FUNCTION_ARGS)
} }
/* /*
* Check for NaN * Check for NaN and infinities. We recognize the same strings allowed by
* float8in().
*/ */
if (pg_strncasecmp(cp, "NaN", 3) == 0) if (pg_strncasecmp(cp, "NaN", 3) == 0)
{ {
res = make_result(&const_nan); res = make_result(&const_nan);
/* Should be nothing left but spaces */
cp += 3; cp += 3;
while (*cp) }
{ else if (pg_strncasecmp(cp, "Infinity", 8) == 0)
if (!isspace((unsigned char) *cp)) {
ereport(ERROR, res = make_result(&const_pinf);
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), cp += 8;
errmsg("invalid input syntax for type %s: \"%s\"", }
"numeric", str))); else if (pg_strncasecmp(cp, "+Infinity", 9) == 0)
cp++; {
} res = make_result(&const_pinf);
cp += 9;
}
else if (pg_strncasecmp(cp, "-Infinity", 9) == 0)
{
res = make_result(&const_ninf);
cp += 9;
}
else if (pg_strncasecmp(cp, "inf", 3) == 0)
{
res = make_result(&const_pinf);
cp += 3;
}
else if (pg_strncasecmp(cp, "+inf", 4) == 0)
{
res = make_result(&const_pinf);
cp += 4;
}
else if (pg_strncasecmp(cp, "-inf", 4) == 0)
{
res = make_result(&const_ninf);
cp += 4;
} }
else else
{ {
...@@ -620,7 +691,7 @@ numeric_in(PG_FUNCTION_ARGS) ...@@ -620,7 +691,7 @@ numeric_in(PG_FUNCTION_ARGS)
* We duplicate a few lines of code here because we would like to * We duplicate a few lines of code here because we would like to
* throw any trailing-junk syntax error before any semantic error * throw any trailing-junk syntax error before any semantic error
* resulting from apply_typmod. We can't easily fold the two cases * resulting from apply_typmod. We can't easily fold the two cases
* together because we mustn't apply apply_typmod to a NaN. * together because we mustn't apply apply_typmod to a NaN/Inf.
*/ */
while (*cp) while (*cp)
{ {
...@@ -636,8 +707,24 @@ numeric_in(PG_FUNCTION_ARGS) ...@@ -636,8 +707,24 @@ numeric_in(PG_FUNCTION_ARGS)
res = make_result(&value); res = make_result(&value);
free_var(&value); free_var(&value);
PG_RETURN_NUMERIC(res);
} }
/* Should be nothing left but spaces */
while (*cp)
{
if (!isspace((unsigned char) *cp))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"numeric", str)));
cp++;
}
/* As above, throw any typmod error after finishing syntax check */
apply_typmod_special(res, typmod);
PG_RETURN_NUMERIC(res); PG_RETURN_NUMERIC(res);
} }
...@@ -655,10 +742,17 @@ numeric_out(PG_FUNCTION_ARGS) ...@@ -655,10 +742,17 @@ numeric_out(PG_FUNCTION_ARGS)
char *str; char *str;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_CSTRING(pstrdup("NaN")); {
if (NUMERIC_IS_PINF(num))
PG_RETURN_CSTRING(pstrdup("Infinity"));
else if (NUMERIC_IS_NINF(num))
PG_RETURN_CSTRING(pstrdup("-Infinity"));
else
PG_RETURN_CSTRING(pstrdup("NaN"));
}
/* /*
* Get the number in the variable format. * Get the number in the variable format.
...@@ -681,6 +775,41 @@ numeric_is_nan(Numeric num) ...@@ -681,6 +775,41 @@ numeric_is_nan(Numeric num)
return NUMERIC_IS_NAN(num); return NUMERIC_IS_NAN(num);
} }
/*
* numeric_is_inf() -
*
* Is Numeric value an infinity?
*/
bool
numeric_is_inf(Numeric num)
{
return NUMERIC_IS_INF(num);
}
/*
* numeric_is_integral() -
*
* Is Numeric value integral?
*/
static bool
numeric_is_integral(Numeric num)
{
NumericVar arg;
/* Reject NaN, but infinities are considered integral */
if (NUMERIC_IS_SPECIAL(num))
{
if (NUMERIC_IS_NAN(num))
return false;
return true;
}
/* Integral if there are no digits to the right of the decimal point */
init_var_from_num(num, &arg);
return (arg.ndigits == 0 || arg.ndigits <= arg.weight + 1);
}
/* /*
* numeric_maximum_size() - * numeric_maximum_size() -
* *
...@@ -732,10 +861,17 @@ numeric_out_sci(Numeric num, int scale) ...@@ -732,10 +861,17 @@ numeric_out_sci(Numeric num, int scale)
char *str; char *str;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
return pstrdup("NaN"); {
if (NUMERIC_IS_PINF(num))
return pstrdup("Infinity");
else if (NUMERIC_IS_NINF(num))
return pstrdup("-Infinity");
else
return pstrdup("NaN");
}
init_var_from_num(num, &x); init_var_from_num(num, &x);
...@@ -760,10 +896,17 @@ numeric_normalize(Numeric num) ...@@ -760,10 +896,17 @@ numeric_normalize(Numeric num)
int last; int last;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
return pstrdup("NaN"); {
if (NUMERIC_IS_PINF(num))
return pstrdup("Infinity");
else if (NUMERIC_IS_NINF(num))
return pstrdup("-Infinity");
else
return pstrdup("NaN");
}
init_var_from_num(num, &x); init_var_from_num(num, &x);
...@@ -823,7 +966,9 @@ numeric_recv(PG_FUNCTION_ARGS) ...@@ -823,7 +966,9 @@ numeric_recv(PG_FUNCTION_ARGS)
value.sign = (uint16) pq_getmsgint(buf, sizeof(uint16)); value.sign = (uint16) pq_getmsgint(buf, sizeof(uint16));
if (!(value.sign == NUMERIC_POS || if (!(value.sign == NUMERIC_POS ||
value.sign == NUMERIC_NEG || value.sign == NUMERIC_NEG ||
value.sign == NUMERIC_NAN)) value.sign == NUMERIC_NAN ||
value.sign == NUMERIC_PINF ||
value.sign == NUMERIC_NINF))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("invalid sign in external \"numeric\" value"))); errmsg("invalid sign in external \"numeric\" value")));
...@@ -849,13 +994,29 @@ numeric_recv(PG_FUNCTION_ARGS) ...@@ -849,13 +994,29 @@ numeric_recv(PG_FUNCTION_ARGS)
* If the given dscale would hide any digits, truncate those digits away. * If the given dscale would hide any digits, truncate those digits away.
* We could alternatively throw an error, but that would take a bunch of * We could alternatively throw an error, but that would take a bunch of
* extra code (about as much as trunc_var involves), and it might cause * extra code (about as much as trunc_var involves), and it might cause
* client compatibility issues. * client compatibility issues. Be careful not to apply trunc_var to
* special values, as it could do the wrong thing; we don't need it
* anyway, since make_result will ignore all but the sign field.
*
* After doing that, be sure to check the typmod restriction.
*/ */
trunc_var(&value, value.dscale); if (value.sign == NUMERIC_POS ||
value.sign == NUMERIC_NEG)
{
trunc_var(&value, value.dscale);
apply_typmod(&value, typmod); apply_typmod(&value, typmod);
res = make_result(&value);
}
else
{
/* apply_typmod_special wants us to make the Numeric first */
res = make_result(&value);
apply_typmod_special(res, typmod);
}
res = make_result(&value);
free_var(&value); free_var(&value);
PG_RETURN_NUMERIC(res); PG_RETURN_NUMERIC(res);
...@@ -961,21 +1122,21 @@ numeric (PG_FUNCTION_ARGS) ...@@ -961,21 +1122,21 @@ numeric (PG_FUNCTION_ARGS)
NumericVar var; NumericVar var;
/* /*
* Handle NaN * Handle NaN and infinities: if apply_typmod_special doesn't complain,
* just return a copy of the input.
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); {
apply_typmod_special(num, typmod);
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
/* /*
* If the value isn't a valid type modifier, simply return a copy of the * If the value isn't a valid type modifier, simply return a copy of the
* input value * input value
*/ */
if (typmod < (int32) (VARHDRSZ)) if (typmod < (int32) (VARHDRSZ))
{ PG_RETURN_NUMERIC(duplicate_numeric(num));
new = (Numeric) palloc(VARSIZE(num));
memcpy(new, num, VARSIZE(num));
PG_RETURN_NUMERIC(new);
}
/* /*
* Get the precision and scale out of the typmod value * Get the precision and scale out of the typmod value
...@@ -997,8 +1158,7 @@ numeric (PG_FUNCTION_ARGS) ...@@ -997,8 +1158,7 @@ numeric (PG_FUNCTION_ARGS)
&& (NUMERIC_CAN_BE_SHORT(scale, NUMERIC_WEIGHT(num)) && (NUMERIC_CAN_BE_SHORT(scale, NUMERIC_WEIGHT(num))
|| !NUMERIC_IS_SHORT(num))) || !NUMERIC_IS_SHORT(num)))
{ {
new = (Numeric) palloc(VARSIZE(num)); new = duplicate_numeric(num);
memcpy(new, num, VARSIZE(num));
if (NUMERIC_IS_SHORT(num)) if (NUMERIC_IS_SHORT(num))
new->choice.n_short.n_header = new->choice.n_short.n_header =
(num->choice.n_short.n_header & ~NUMERIC_SHORT_DSCALE_MASK) (num->choice.n_short.n_header & ~NUMERIC_SHORT_DSCALE_MASK)
...@@ -1099,21 +1259,20 @@ numeric_abs(PG_FUNCTION_ARGS) ...@@ -1099,21 +1259,20 @@ numeric_abs(PG_FUNCTION_ARGS)
Numeric num = PG_GETARG_NUMERIC(0); Numeric num = PG_GETARG_NUMERIC(0);
Numeric res; Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
* Do it the easy way directly on the packed format * Do it the easy way directly on the packed format
*/ */
res = (Numeric) palloc(VARSIZE(num)); res = duplicate_numeric(num);
memcpy(res, num, VARSIZE(num));
if (NUMERIC_IS_SHORT(num)) if (NUMERIC_IS_SHORT(num))
res->choice.n_short.n_header = res->choice.n_short.n_header =
num->choice.n_short.n_header & ~NUMERIC_SHORT_SIGN_MASK; num->choice.n_short.n_header & ~NUMERIC_SHORT_SIGN_MASK;
else if (NUMERIC_IS_SPECIAL(num))
{
/* This changes -Inf to Inf, and doesn't affect NaN */
res->choice.n_short.n_header =
num->choice.n_short.n_header & ~NUMERIC_INF_SIGN_MASK;
}
else else
res->choice.n_long.n_sign_dscale = NUMERIC_POS | NUMERIC_DSCALE(num); res->choice.n_long.n_sign_dscale = NUMERIC_POS | NUMERIC_DSCALE(num);
...@@ -1127,24 +1286,25 @@ numeric_uminus(PG_FUNCTION_ARGS) ...@@ -1127,24 +1286,25 @@ numeric_uminus(PG_FUNCTION_ARGS)
Numeric num = PG_GETARG_NUMERIC(0); Numeric num = PG_GETARG_NUMERIC(0);
Numeric res; Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
* Do it the easy way directly on the packed format * Do it the easy way directly on the packed format
*/ */
res = (Numeric) palloc(VARSIZE(num)); res = duplicate_numeric(num);
memcpy(res, num, VARSIZE(num));
if (NUMERIC_IS_SPECIAL(num))
{
/* Flip the sign, if it's Inf or -Inf */
if (!NUMERIC_IS_NAN(num))
res->choice.n_short.n_header =
num->choice.n_short.n_header ^ NUMERIC_INF_SIGN_MASK;
}
/* /*
* The packed format is known to be totally zero digit trimmed always. So * The packed format is known to be totally zero digit trimmed always. So
* we can identify a ZERO by the fact that there are no digits at all. Do * once we've eliminated specials, we can identify a zero by the fact that
* nothing to a zero. * there are no digits at all. Do nothing to a zero.
*/ */
if (NUMERIC_NDIGITS(num) != 0) else if (NUMERIC_NDIGITS(num) != 0)
{ {
/* Else, flip the sign */ /* Else, flip the sign */
if (NUMERIC_IS_SHORT(num)) if (NUMERIC_IS_SHORT(num))
...@@ -1166,12 +1326,42 @@ Datum ...@@ -1166,12 +1326,42 @@ Datum
numeric_uplus(PG_FUNCTION_ARGS) numeric_uplus(PG_FUNCTION_ARGS)
{ {
Numeric num = PG_GETARG_NUMERIC(0); Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
res = (Numeric) palloc(VARSIZE(num)); PG_RETURN_NUMERIC(duplicate_numeric(num));
memcpy(res, num, VARSIZE(num)); }
PG_RETURN_NUMERIC(res);
/*
* numeric_sign_internal() -
*
* 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. Caller must have
* taken care of the NaN case, but we can handle infinities here.
*/
static int
numeric_sign_internal(Numeric num)
{
if (NUMERIC_IS_SPECIAL(num))
{
Assert(!NUMERIC_IS_NAN(num));
/* Must be Inf or -Inf */
if (NUMERIC_IS_PINF(num))
return 1;
else
return -1;
}
/*
* The packed format is known to be totally zero digit trimmed always. So
* once we've eliminated specials, we can identify a zero by the fact that
* there are no digits at all.
*/
else if (NUMERIC_NDIGITS(num) == 0)
return 0;
else if (NUMERIC_SIGN(num) == NUMERIC_NEG)
return -1;
else
return 1;
} }
/* /*
...@@ -1184,37 +1374,25 @@ Datum ...@@ -1184,37 +1374,25 @@ Datum
numeric_sign(PG_FUNCTION_ARGS) numeric_sign(PG_FUNCTION_ARGS)
{ {
Numeric num = PG_GETARG_NUMERIC(0); Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
NumericVar result;
/* /*
* Handle NaN * Handle NaN (infinities can be handled normally)
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
init_var(&result); switch (numeric_sign_internal(num))
/*
* The packed format is known to be totally zero digit trimmed always. So
* we can identify a ZERO by the fact that there are no digits at all.
*/
if (NUMERIC_NDIGITS(num) == 0)
set_var_from_var(&const_zero, &result);
else
{ {
/* case 0:
* And if there are some, we return a copy of ONE with the sign of our PG_RETURN_NUMERIC(make_result(&const_zero));
* argument case 1:
*/ PG_RETURN_NUMERIC(make_result(&const_one));
set_var_from_var(&const_one, &result); case -1:
result.sign = NUMERIC_SIGN(num); PG_RETURN_NUMERIC(make_result(&const_minus_one));
} }
res = make_result(&result); Assert(false);
free_var(&result); return (Datum) 0;
PG_RETURN_NUMERIC(res);
} }
...@@ -1234,10 +1412,10 @@ numeric_round(PG_FUNCTION_ARGS) ...@@ -1234,10 +1412,10 @@ numeric_round(PG_FUNCTION_ARGS)
NumericVar arg; NumericVar arg;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(duplicate_numeric(num));
/* /*
* Limit the scale value to avoid possible overflow in calculations * Limit the scale value to avoid possible overflow in calculations
...@@ -1283,10 +1461,10 @@ numeric_trunc(PG_FUNCTION_ARGS) ...@@ -1283,10 +1461,10 @@ numeric_trunc(PG_FUNCTION_ARGS)
NumericVar arg; NumericVar arg;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(duplicate_numeric(num));
/* /*
* Limit the scale value to avoid possible overflow in calculations * Limit the scale value to avoid possible overflow in calculations
...@@ -1328,8 +1506,11 @@ numeric_ceil(PG_FUNCTION_ARGS) ...@@ -1328,8 +1506,11 @@ numeric_ceil(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
NumericVar result; NumericVar result;
if (NUMERIC_IS_NAN(num)) /*
PG_RETURN_NUMERIC(make_result(&const_nan)); * Handle NaN and infinities
*/
if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(duplicate_numeric(num));
init_var_from_num(num, &result); init_var_from_num(num, &result);
ceil_var(&result, &result); ceil_var(&result, &result);
...@@ -1353,8 +1534,11 @@ numeric_floor(PG_FUNCTION_ARGS) ...@@ -1353,8 +1534,11 @@ numeric_floor(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
NumericVar result; NumericVar result;
if (NUMERIC_IS_NAN(num)) /*
PG_RETURN_NUMERIC(make_result(&const_nan)); * Handle NaN and infinities
*/
if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(duplicate_numeric(num));
init_var_from_num(num, &result); init_var_from_num(num, &result);
floor_var(&result, &result); floor_var(&result, &result);
...@@ -1390,26 +1574,46 @@ generate_series_step_numeric(PG_FUNCTION_ARGS) ...@@ -1390,26 +1574,46 @@ generate_series_step_numeric(PG_FUNCTION_ARGS)
Numeric stop_num = PG_GETARG_NUMERIC(1); Numeric stop_num = PG_GETARG_NUMERIC(1);
NumericVar steploc = const_one; NumericVar steploc = const_one;
/* handle NaN in start and stop values */ /* Reject NaN and infinities in start and stop values */
if (NUMERIC_IS_NAN(start_num)) if (NUMERIC_IS_SPECIAL(start_num))
ereport(ERROR, {
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), if (NUMERIC_IS_NAN(start_num))
errmsg("start value cannot be NaN"))); ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
if (NUMERIC_IS_NAN(stop_num)) errmsg("start value cannot be NaN")));
ereport(ERROR, else
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), ereport(ERROR,
errmsg("stop value cannot be NaN"))); (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("start value cannot be infinity")));
}
if (NUMERIC_IS_SPECIAL(stop_num))
{
if (NUMERIC_IS_NAN(stop_num))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("stop value cannot be NaN")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("stop value cannot be infinity")));
}
/* see if we were given an explicit step size */ /* see if we were given an explicit step size */
if (PG_NARGS() == 3) if (PG_NARGS() == 3)
{ {
Numeric step_num = PG_GETARG_NUMERIC(2); Numeric step_num = PG_GETARG_NUMERIC(2);
if (NUMERIC_IS_NAN(step_num)) if (NUMERIC_IS_SPECIAL(step_num))
ereport(ERROR, {
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), if (NUMERIC_IS_NAN(step_num))
errmsg("step size cannot be NaN"))); ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot be NaN")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot be infinity")));
}
init_var_from_num(step_num, &steploc); init_var_from_num(step_num, &steploc);
...@@ -1510,12 +1714,21 @@ width_bucket_numeric(PG_FUNCTION_ARGS) ...@@ -1510,12 +1714,21 @@ width_bucket_numeric(PG_FUNCTION_ARGS)
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
errmsg("count must be greater than zero"))); errmsg("count must be greater than zero")));
if (NUMERIC_IS_NAN(operand) || if (NUMERIC_IS_SPECIAL(operand) ||
NUMERIC_IS_NAN(bound1) || NUMERIC_IS_SPECIAL(bound1) ||
NUMERIC_IS_NAN(bound2)) NUMERIC_IS_SPECIAL(bound2))
ereport(ERROR, {
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), if (NUMERIC_IS_NAN(operand) ||
errmsg("operand, lower bound, and upper bound cannot be NaN"))); NUMERIC_IS_NAN(bound1) ||
NUMERIC_IS_NAN(bound2))
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
errmsg("operand, lower bound, and upper bound cannot be NaN")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
errmsg("operand, lower bound, and upper bound cannot be infinity")));
}
init_var(&result_var); init_var(&result_var);
init_var(&count_var); init_var(&count_var);
...@@ -1719,9 +1932,14 @@ numeric_abbrev_convert(Datum original_datum, SortSupport ssup) ...@@ -1719,9 +1932,14 @@ numeric_abbrev_convert(Datum original_datum, SortSupport ssup)
else else
value = (Numeric) original_varatt; value = (Numeric) original_varatt;
if (NUMERIC_IS_NAN(value)) if (NUMERIC_IS_SPECIAL(value))
{ {
result = NUMERIC_ABBREV_NAN; if (NUMERIC_IS_PINF(value))
result = NUMERIC_ABBREV_PINF;
else if (NUMERIC_IS_NINF(value))
result = NUMERIC_ABBREV_NINF;
else
result = NUMERIC_ABBREV_NAN;
} }
else else
{ {
...@@ -1847,7 +2065,7 @@ numeric_cmp_abbrev(Datum x, Datum y, SortSupport ssup) ...@@ -1847,7 +2065,7 @@ numeric_cmp_abbrev(Datum x, Datum y, SortSupport ssup)
{ {
/* /*
* NOTE WELL: this is intentionally backwards, because the abbreviation is * NOTE WELL: this is intentionally backwards, because the abbreviation is
* negated relative to the original value, to handle NaN. * negated relative to the original value, to handle NaN/infinity cases.
*/ */
if (DatumGetNumericAbbrev(x) < DatumGetNumericAbbrev(y)) if (DatumGetNumericAbbrev(x) < DatumGetNumericAbbrev(y))
return 1; return 1;
...@@ -2150,20 +2368,42 @@ cmp_numerics(Numeric num1, Numeric num2) ...@@ -2150,20 +2368,42 @@ cmp_numerics(Numeric num1, Numeric num2)
int result; int result;
/* /*
* We consider all NANs to be equal and larger than any non-NAN. This is * We consider all NANs to be equal and larger than any non-NAN (including
* somewhat arbitrary; the important thing is to have a consistent sort * Infinity). This is somewhat arbitrary; the important thing is to have
* order. * a consistent sort order.
*/ */
if (NUMERIC_IS_NAN(num1)) if (NUMERIC_IS_SPECIAL(num1))
{ {
if (NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_NAN(num1))
result = 0; /* NAN = NAN */ {
else if (NUMERIC_IS_NAN(num2))
result = 1; /* NAN > non-NAN */ result = 0; /* NAN = NAN */
else
result = 1; /* NAN > non-NAN */
}
else if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_NAN(num2))
result = -1; /* PINF < NAN */
else if (NUMERIC_IS_PINF(num2))
result = 0; /* PINF = PINF */
else
result = 1; /* PINF > anything else */
}
else /* num1 must be NINF */
{
if (NUMERIC_IS_NINF(num2))
result = 0; /* NINF = NINF */
else
result = -1; /* NINF < anything else */
}
} }
else if (NUMERIC_IS_NAN(num2)) else if (NUMERIC_IS_SPECIAL(num2))
{ {
result = -1; /* non-NAN < NAN */ if (NUMERIC_IS_NINF(num2))
result = 1; /* normal > NINF */
else
result = -1; /* normal < NAN or PINF */
} }
else else
{ {
...@@ -2190,10 +2430,12 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS) ...@@ -2190,10 +2430,12 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
bool result; bool result;
/* /*
* Reject negative or NaN offset. Negative is per spec, and NaN is * Reject negative (including -Inf) or NaN offset. Negative is per spec,
* because appropriate semantics for that seem non-obvious. * and NaN is because appropriate semantics for that seem non-obvious.
*/ */
if (NUMERIC_IS_NAN(offset) || NUMERIC_SIGN(offset) == NUMERIC_NEG) if (NUMERIC_IS_NAN(offset) ||
NUMERIC_IS_NINF(offset) ||
NUMERIC_SIGN(offset) == NUMERIC_NEG)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function"))); errmsg("invalid preceding or following size in window function")));
...@@ -2214,6 +2456,67 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS) ...@@ -2214,6 +2456,67 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
{ {
result = less; /* non-NAN < NAN */ result = less; /* non-NAN < NAN */
} }
/*
* Deal with infinite offset (necessarily +Inf, at this point).
*/
else if (NUMERIC_IS_SPECIAL(offset))
{
Assert(NUMERIC_IS_PINF(offset));
if (sub ? NUMERIC_IS_PINF(base) : NUMERIC_IS_NINF(base))
{
/*
* base +/- offset would produce NaN, so return true for any val
* (see in_range_float8_float8() for reasoning).
*/
result = true;
}
else if (sub)
{
/* base - offset must be -inf */
if (less)
result = NUMERIC_IS_NINF(val); /* only -inf is <= sum */
else
result = true; /* any val is >= sum */
}
else
{
/* base + offset must be +inf */
if (less)
result = true; /* any val is <= sum */
else
result = NUMERIC_IS_PINF(val); /* only +inf is >= sum */
}
}
/*
* Deal with cases where val and/or base is infinite. The offset, being
* now known finite, cannot affect the conclusion.
*/
else if (NUMERIC_IS_SPECIAL(val))
{
if (NUMERIC_IS_PINF(val))
{
if (NUMERIC_IS_PINF(base))
result = true; /* PINF = PINF */
else
result = !less; /* PINF > any other non-NAN */
}
else /* val must be NINF */
{
if (NUMERIC_IS_NINF(base))
result = true; /* NINF = NINF */
else
result = less; /* NINF < anything else */
}
}
else if (NUMERIC_IS_SPECIAL(base))
{
if (NUMERIC_IS_NINF(base))
result = !less; /* normal > NINF */
else
result = less; /* normal < PINF */
}
else else
{ {
/* /*
...@@ -2264,8 +2567,8 @@ hash_numeric(PG_FUNCTION_ARGS) ...@@ -2264,8 +2567,8 @@ hash_numeric(PG_FUNCTION_ARGS)
int hash_len; int hash_len;
NumericDigit *digits; NumericDigit *digits;
/* If it's NaN, don't try to hash the rest of the fields */ /* If it's NaN or infinity, don't try to hash the rest of the fields */
if (NUMERIC_IS_NAN(key)) if (NUMERIC_IS_SPECIAL(key))
PG_RETURN_UINT32(0); PG_RETURN_UINT32(0);
weight = NUMERIC_WEIGHT(key); weight = NUMERIC_WEIGHT(key);
...@@ -2345,7 +2648,8 @@ hash_numeric_extended(PG_FUNCTION_ARGS) ...@@ -2345,7 +2648,8 @@ hash_numeric_extended(PG_FUNCTION_ARGS)
int hash_len; int hash_len;
NumericDigit *digits; NumericDigit *digits;
if (NUMERIC_IS_NAN(key)) /* If it's NaN or infinity, don't try to hash the rest of the fields */
if (NUMERIC_IS_SPECIAL(key))
PG_RETURN_UINT64(seed); PG_RETURN_UINT64(seed);
weight = NUMERIC_WEIGHT(key); weight = NUMERIC_WEIGHT(key);
...@@ -2429,10 +2733,32 @@ numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error) ...@@ -2429,10 +2733,32 @@ numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error)
Numeric res; Numeric res;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
return make_result(&const_nan); {
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_NINF(num2))
return make_result(&const_nan); /* Inf + -Inf */
else
return make_result(&const_pinf);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_PINF(num2))
return make_result(&const_nan); /* -Inf + Inf */
else
return make_result(&const_ninf);
}
/* by here, num1 must be finite, so num2 is not */
if (NUMERIC_IS_PINF(num2))
return make_result(&const_pinf);
Assert(NUMERIC_IS_NINF(num2));
return make_result(&const_ninf);
}
/* /*
* Unpack the values, let add_var() compute the result and return it. * Unpack the values, let add_var() compute the result and return it.
...@@ -2485,10 +2811,32 @@ numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error) ...@@ -2485,10 +2811,32 @@ numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error)
Numeric res; Numeric res;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
return make_result(&const_nan); {
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_PINF(num2))
return make_result(&const_nan); /* Inf - Inf */
else
return make_result(&const_pinf);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_NINF(num2))
return make_result(&const_nan); /* -Inf - -Inf */
else
return make_result(&const_ninf);
}
/* by here, num1 must be finite, so num2 is not */
if (NUMERIC_IS_PINF(num2))
return make_result(&const_ninf);
Assert(NUMERIC_IS_NINF(num2));
return make_result(&const_pinf);
}
/* /*
* Unpack the values, let sub_var() compute the result and return it. * Unpack the values, let sub_var() compute the result and return it.
...@@ -2541,10 +2889,64 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error) ...@@ -2541,10 +2889,64 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
Numeric res; Numeric res;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
return make_result(&const_nan); {
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
switch (numeric_sign_internal(num2))
{
case 0:
return make_result(&const_nan); /* Inf * 0 */
case 1:
return make_result(&const_pinf);
case -1:
return make_result(&const_ninf);
}
Assert(false);
}
if (NUMERIC_IS_NINF(num1))
{
switch (numeric_sign_internal(num2))
{
case 0:
return make_result(&const_nan); /* -Inf * 0 */
case 1:
return make_result(&const_ninf);
case -1:
return make_result(&const_pinf);
}
Assert(false);
}
/* by here, num1 must be finite, so num2 is not */
if (NUMERIC_IS_PINF(num2))
{
switch (numeric_sign_internal(num1))
{
case 0:
return make_result(&const_nan); /* 0 * Inf */
case 1:
return make_result(&const_pinf);
case -1:
return make_result(&const_ninf);
}
Assert(false);
}
Assert(NUMERIC_IS_NINF(num2));
switch (numeric_sign_internal(num1))
{
case 0:
return make_result(&const_nan); /* 0 * -Inf */
case 1:
return make_result(&const_ninf);
case -1:
return make_result(&const_pinf);
}
Assert(false);
}
/* /*
* Unpack the values, let mul_var() compute the result and return it. * Unpack the values, let mul_var() compute the result and return it.
...@@ -2605,10 +3007,67 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error) ...@@ -2605,10 +3007,67 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
*have_error = false; *have_error = false;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
return make_result(&const_nan); {
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
return make_result(&const_nan); /* Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
if (have_error)
{
*have_error = true;
return NULL;
}
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
break;
case 1:
return make_result(&const_pinf);
case -1:
return make_result(&const_ninf);
}
Assert(false);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
return make_result(&const_nan); /* -Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
if (have_error)
{
*have_error = true;
return NULL;
}
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
break;
case 1:
return make_result(&const_ninf);
case -1:
return make_result(&const_pinf);
}
Assert(false);
}
/* by here, num1 must be finite, so num2 is not */
/*
* POSIX would have us return zero or minus zero if num1 is zero, and
* otherwise throw an underflow error. But the numeric type doesn't
* really do underflow, so let's just return zero.
*/
return make_result(&const_zero);
}
/* /*
* Unpack the arguments * Unpack the arguments
...@@ -2661,10 +3120,57 @@ numeric_div_trunc(PG_FUNCTION_ARGS) ...@@ -2661,10 +3120,57 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan)); {
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
if (NUMERIC_IS_PINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan)); /* Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
break;
case 1:
PG_RETURN_NUMERIC(make_result(&const_pinf));
case -1:
PG_RETURN_NUMERIC(make_result(&const_ninf));
}
Assert(false);
}
if (NUMERIC_IS_NINF(num1))
{
if (NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan)); /* -Inf / [-]Inf */
switch (numeric_sign_internal(num2))
{
case 0:
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
break;
case 1:
PG_RETURN_NUMERIC(make_result(&const_ninf));
case -1:
PG_RETURN_NUMERIC(make_result(&const_pinf));
}
Assert(false);
}
/* by here, num1 must be finite, so num2 is not */
/*
* POSIX would have us return zero or minus zero if num1 is zero, and
* otherwise throw an underflow error. But the numeric type doesn't
* really do underflow, so let's just return zero.
*/
PG_RETURN_NUMERIC(make_result(&const_zero));
}
/* /*
* Unpack the arguments * Unpack the arguments
...@@ -2723,8 +3229,34 @@ numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error) ...@@ -2723,8 +3229,34 @@ numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error)
if (have_error) if (have_error)
*have_error = false; *have_error = false;
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) /*
return make_result(&const_nan); * Handle NaN and infinities. We follow POSIX fmod() on this, except that
* POSIX treats x-is-infinite and y-is-zero identically, raising EDOM and
* returning NaN. We choose to throw error only for y-is-zero.
*/
if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
if (NUMERIC_IS_INF(num1))
{
if (numeric_sign_internal(num2) == 0)
{
if (have_error)
{
*have_error = true;
return NULL;
}
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
}
/* Inf % any nonzero = NaN */
return make_result(&const_nan);
}
/* num2 must be [-]Inf; result is num1 regardless of sign of num2 */
return duplicate_numeric(num1);
}
init_var_from_num(num1, &arg1); init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2); init_var_from_num(num2, &arg2);
...@@ -2763,10 +3295,10 @@ numeric_inc(PG_FUNCTION_ARGS) ...@@ -2763,10 +3295,10 @@ numeric_inc(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(duplicate_numeric(num));
/* /*
* Compute the result and return it * Compute the result and return it
...@@ -2850,9 +3382,10 @@ numeric_gcd(PG_FUNCTION_ARGS) ...@@ -2850,9 +3382,10 @@ numeric_gcd(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
/* /*
* Handle NaN * Handle NaN and infinities: we consider the result to be NaN in all such
* cases.
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
...@@ -2892,9 +3425,10 @@ numeric_lcm(PG_FUNCTION_ARGS) ...@@ -2892,9 +3425,10 @@ numeric_lcm(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
/* /*
* Handle NaN * Handle NaN and infinities: we consider the result to be NaN in all such
* cases.
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
...@@ -3003,10 +3537,18 @@ numeric_sqrt(PG_FUNCTION_ARGS) ...@@ -3003,10 +3537,18 @@ numeric_sqrt(PG_FUNCTION_ARGS)
int rscale; int rscale;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); {
/* error should match that in sqrt_var() */
if (NUMERIC_IS_NINF(num))
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("cannot take square root of a negative number")));
/* For NAN or PINF, just duplicate the input */
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
/* /*
* Unpack the argument and determine the result scale. We choose a scale * Unpack the argument and determine the result scale. We choose a scale
...@@ -3054,10 +3596,16 @@ numeric_exp(PG_FUNCTION_ARGS) ...@@ -3054,10 +3596,16 @@ numeric_exp(PG_FUNCTION_ARGS)
double val; double val;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); {
/* Per POSIX, exp(-Inf) is zero */
if (NUMERIC_IS_NINF(num))
PG_RETURN_NUMERIC(make_result(&const_zero));
/* For NAN or PINF, just duplicate the input */
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
/* /*
* Unpack the argument and determine the result scale. We choose a scale * Unpack the argument and determine the result scale. We choose a scale
...@@ -3115,10 +3663,17 @@ numeric_ln(PG_FUNCTION_ARGS) ...@@ -3115,10 +3663,17 @@ numeric_ln(PG_FUNCTION_ARGS)
int rscale; int rscale;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); {
if (NUMERIC_IS_NINF(num))
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
errmsg("cannot take logarithm of a negative number")));
/* For NAN or PINF, just duplicate the input */
PG_RETURN_NUMERIC(duplicate_numeric(num));
}
init_var_from_num(num, &arg); init_var_from_num(num, &arg);
init_var(&result); init_var(&result);
...@@ -3157,10 +3712,39 @@ numeric_log(PG_FUNCTION_ARGS) ...@@ -3157,10 +3712,39 @@ numeric_log(PG_FUNCTION_ARGS)
NumericVar result; NumericVar result;
/* /*
* Handle NaN * Handle NaN and infinities
*/ */
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan)); {
int sign1,
sign2;
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/* fail on negative inputs including -Inf, as log_var would */
sign1 = numeric_sign_internal(num1);
sign2 = numeric_sign_internal(num2);
if (sign1 < 0 || sign2 < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
errmsg("cannot take logarithm of a negative number")));
/* fail on zero inputs, as log_var would */
if (sign1 == 0 || sign2 == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
errmsg("cannot take logarithm of zero")));
if (NUMERIC_IS_PINF(num1))
{
/* log(Inf, Inf) reduces to Inf/Inf, so it's NaN */
if (NUMERIC_IS_PINF(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/* log(Inf, finite-positive) is zero (we don't throw underflow) */
PG_RETURN_NUMERIC(make_result(&const_zero));
}
Assert(NUMERIC_IS_PINF(num2));
/* log(finite-positive, Inf) is Inf */
PG_RETURN_NUMERIC(make_result(&const_pinf));
}
/* /*
* Initialize things * Initialize things
...@@ -3186,7 +3770,7 @@ numeric_log(PG_FUNCTION_ARGS) ...@@ -3186,7 +3770,7 @@ numeric_log(PG_FUNCTION_ARGS)
/* /*
* numeric_power() - * numeric_power() -
* *
* Raise b to the power of x * Raise x to the power of y
*/ */
Datum Datum
numeric_power(PG_FUNCTION_ARGS) numeric_power(PG_FUNCTION_ARGS)
...@@ -3196,60 +3780,170 @@ numeric_power(PG_FUNCTION_ARGS) ...@@ -3196,60 +3780,170 @@ numeric_power(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
NumericVar arg1; NumericVar arg1;
NumericVar arg2; NumericVar arg2;
NumericVar arg2_trunc;
NumericVar result; NumericVar result;
int sign1,
sign2;
/* /*
* Handle NaN cases. We follow the POSIX spec for pow(3), which says that * Handle NaN and infinities
* NaN ^ 0 = 1, and 1 ^ NaN = 1, while all other cases with NaN inputs
* yield NaN (with no error).
*/ */
if (NUMERIC_IS_NAN(num1)) if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{ {
if (!NUMERIC_IS_NAN(num2)) /*
* We follow the POSIX spec for pow(3), which says that NaN ^ 0 = 1,
* and 1 ^ NaN = 1, while all other cases with NaN inputs yield NaN
* (with no error).
*/
if (NUMERIC_IS_NAN(num1))
{
if (!NUMERIC_IS_SPECIAL(num2))
{
init_var_from_num(num2, &arg2);
if (cmp_var(&arg2, &const_zero) == 0)
PG_RETURN_NUMERIC(make_result(&const_one));
}
PG_RETURN_NUMERIC(make_result(&const_nan));
}
if (NUMERIC_IS_NAN(num2))
{
if (!NUMERIC_IS_SPECIAL(num1))
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_one) == 0)
PG_RETURN_NUMERIC(make_result(&const_one));
}
PG_RETURN_NUMERIC(make_result(&const_nan));
}
/* At least one input is infinite, but error rules still apply */
sign1 = numeric_sign_internal(num1);
sign2 = numeric_sign_internal(num2);
if (sign1 == 0 && sign2 < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("zero raised to a negative power is undefined")));
if (sign1 < 0 && !numeric_is_integral(num2))
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("a negative number raised to a non-integer power yields a complex result")));
/*
* POSIX gives this series of rules for pow(3) with infinite inputs:
*
* For any value of y, if x is +1, 1.0 shall be returned.
*/
if (!NUMERIC_IS_SPECIAL(num1))
{ {
init_var_from_num(num2, &arg2); init_var_from_num(num1, &arg1);
if (cmp_var(&arg2, &const_zero) == 0) if (cmp_var(&arg1, &const_one) == 0)
PG_RETURN_NUMERIC(make_result(&const_one)); PG_RETURN_NUMERIC(make_result(&const_one));
} }
PG_RETURN_NUMERIC(make_result(&const_nan));
} /*
if (NUMERIC_IS_NAN(num2)) * For any value of x, if y is [-]0, 1.0 shall be returned.
{ */
init_var_from_num(num1, &arg1); if (sign2 == 0)
if (cmp_var(&arg1, &const_one) == 0)
PG_RETURN_NUMERIC(make_result(&const_one)); PG_RETURN_NUMERIC(make_result(&const_one));
PG_RETURN_NUMERIC(make_result(&const_nan));
}
/* /*
* Initialize things * For any odd integer value of y > 0, if x is [-]0, [-]0 shall be
*/ * returned. For y > 0 and not an odd integer, if x is [-]0, +0 shall
init_var(&arg2_trunc); * be returned. (Since we don't deal in minus zero, we need not
init_var(&result); * distinguish these two cases.)
init_var_from_num(num1, &arg1); */
init_var_from_num(num2, &arg2); if (sign1 == 0 && sign2 > 0)
PG_RETURN_NUMERIC(make_result(&const_zero));
/*
* If x is -1, and y is [-]Inf, 1.0 shall be returned.
*
* For |x| < 1, if y is -Inf, +Inf shall be returned.
*
* For |x| > 1, if y is -Inf, +0 shall be returned.
*
* For |x| < 1, if y is +Inf, +0 shall be returned.
*
* For |x| > 1, if y is +Inf, +Inf shall be returned.
*/
if (NUMERIC_IS_INF(num2))
{
bool abs_x_gt_one;
if (NUMERIC_IS_SPECIAL(num1))
abs_x_gt_one = true; /* x is either Inf or -Inf */
else
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_minus_one) == 0)
PG_RETURN_NUMERIC(make_result(&const_one));
arg1.sign = NUMERIC_POS; /* now arg1 = abs(x) */
abs_x_gt_one = (cmp_var(&arg1, &const_one) > 0);
}
if (abs_x_gt_one == (sign2 > 0))
PG_RETURN_NUMERIC(make_result(&const_pinf));
else
PG_RETURN_NUMERIC(make_result(&const_zero));
}
/*
* For y < 0, if x is +Inf, +0 shall be returned.
*
* For y > 0, if x is +Inf, +Inf shall be returned.
*/
if (NUMERIC_IS_PINF(num1))
{
if (sign2 > 0)
PG_RETURN_NUMERIC(make_result(&const_pinf));
else
PG_RETURN_NUMERIC(make_result(&const_zero));
}
Assert(NUMERIC_IS_NINF(num1));
/*
* For y an odd integer < 0, if x is -Inf, -0 shall be returned. For
* y < 0 and not an odd integer, if x is -Inf, +0 shall be returned.
* (Again, we need not distinguish these two cases.)
*/
if (sign2 < 0)
PG_RETURN_NUMERIC(make_result(&const_zero));
set_var_from_var(&arg2, &arg2_trunc); /*
trunc_var(&arg2_trunc, 0); * For y an odd integer > 0, if x is -Inf, -Inf shall be returned. For
* y > 0 and not an odd integer, if x is -Inf, +Inf shall be returned.
*/
init_var_from_num(num2, &arg2);
if (arg2.ndigits > 0 && arg2.ndigits == arg2.weight + 1 &&
(arg2.digits[arg2.ndigits - 1] & 1))
PG_RETURN_NUMERIC(make_result(&const_ninf));
else
PG_RETURN_NUMERIC(make_result(&const_pinf));
}
/* /*
* The SQL spec requires that we emit a particular SQLSTATE error code for * The SQL spec requires that we emit a particular SQLSTATE error code for
* certain error conditions. Specifically, we don't return a * certain error conditions. Specifically, we don't return a
* divide-by-zero error code for 0 ^ -1. * divide-by-zero error code for 0 ^ -1.
*/ */
if (cmp_var(&arg1, &const_zero) == 0 && sign1 = numeric_sign_internal(num1);
cmp_var(&arg2, &const_zero) < 0) sign2 = numeric_sign_internal(num2);
if (sign1 == 0 && sign2 < 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("zero raised to a negative power is undefined"))); errmsg("zero raised to a negative power is undefined")));
if (cmp_var(&arg1, &const_zero) < 0 && if (sign1 < 0 && !numeric_is_integral(num2))
cmp_var(&arg2, &arg2_trunc) != 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("a negative number raised to a non-integer power yields a complex result"))); errmsg("a negative number raised to a non-integer power yields a complex result")));
/*
* Initialize things
*/
init_var(&result);
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
/* /*
* Call power_var() to compute and return the result; note it handles * Call power_var() to compute and return the result; note it handles
* scale selection itself. * scale selection itself.
...@@ -3259,7 +3953,6 @@ numeric_power(PG_FUNCTION_ARGS) ...@@ -3259,7 +3953,6 @@ numeric_power(PG_FUNCTION_ARGS)
res = make_result(&result); res = make_result(&result);
free_var(&result); free_var(&result);
free_var(&arg2_trunc);
PG_RETURN_NUMERIC(res); PG_RETURN_NUMERIC(res);
} }
...@@ -3274,7 +3967,7 @@ numeric_scale(PG_FUNCTION_ARGS) ...@@ -3274,7 +3967,7 @@ numeric_scale(PG_FUNCTION_ARGS)
{ {
Numeric num = PG_GETARG_NUMERIC(0); Numeric num = PG_GETARG_NUMERIC(0);
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NULL(); PG_RETURN_NULL();
PG_RETURN_INT32(NUMERIC_DSCALE(num)); PG_RETURN_INT32(NUMERIC_DSCALE(num));
...@@ -3341,7 +4034,7 @@ numeric_min_scale(PG_FUNCTION_ARGS) ...@@ -3341,7 +4034,7 @@ numeric_min_scale(PG_FUNCTION_ARGS)
NumericVar arg; NumericVar arg;
int min_scale; int min_scale;
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NULL(); PG_RETURN_NULL();
init_var_from_num(num, &arg); init_var_from_num(num, &arg);
...@@ -3361,8 +4054,8 @@ numeric_trim_scale(PG_FUNCTION_ARGS) ...@@ -3361,8 +4054,8 @@ numeric_trim_scale(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
NumericVar result; NumericVar result;
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(duplicate_numeric(num));
init_var_from_num(num, &result); init_var_from_num(num, &result);
result.dscale = get_min_scale(&result); result.dscale = get_min_scale(&result);
...@@ -3408,8 +4101,7 @@ numeric_int4_opt_error(Numeric num, bool *have_error) ...@@ -3408,8 +4101,7 @@ numeric_int4_opt_error(Numeric num, bool *have_error)
if (have_error) if (have_error)
*have_error = false; *have_error = false;
/* XXX would it be better to return NULL? */ if (NUMERIC_IS_SPECIAL(num))
if (NUMERIC_IS_NAN(num))
{ {
if (have_error) if (have_error)
{ {
...@@ -3418,9 +4110,14 @@ numeric_int4_opt_error(Numeric num, bool *have_error) ...@@ -3418,9 +4110,14 @@ numeric_int4_opt_error(Numeric num, bool *have_error)
} }
else else
{ {
ereport(ERROR, if (NUMERIC_IS_NAN(num))
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR,
errmsg("cannot convert NaN to integer"))); (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to integer")));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to integer")));
} }
} }
...@@ -3499,11 +4196,17 @@ numeric_int8(PG_FUNCTION_ARGS) ...@@ -3499,11 +4196,17 @@ numeric_int8(PG_FUNCTION_ARGS)
NumericVar x; NumericVar x;
int64 result; int64 result;
/* XXX would it be better to return NULL? */ if (NUMERIC_IS_SPECIAL(num))
if (NUMERIC_IS_NAN(num)) {
ereport(ERROR, if (NUMERIC_IS_NAN(num))
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR,
errmsg("cannot convert NaN to bigint"))); (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to bigint")));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to bigint")));
}
/* Convert to variable format and thence to int8 */ /* Convert to variable format and thence to int8 */
init_var_from_num(num, &x); init_var_from_num(num, &x);
...@@ -3544,11 +4247,17 @@ numeric_int2(PG_FUNCTION_ARGS) ...@@ -3544,11 +4247,17 @@ numeric_int2(PG_FUNCTION_ARGS)
int64 val; int64 val;
int16 result; int16 result;
/* XXX would it be better to return NULL? */ if (NUMERIC_IS_SPECIAL(num))
if (NUMERIC_IS_NAN(num)) {
ereport(ERROR, if (NUMERIC_IS_NAN(num))
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR,
errmsg("cannot convert NaN to smallint"))); (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to smallint")));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to smallint")));
}
/* Convert to variable format and thence to int8 */ /* Convert to variable format and thence to int8 */
init_var_from_num(num, &x); init_var_from_num(num, &x);
...@@ -3583,9 +4292,12 @@ float8_numeric(PG_FUNCTION_ARGS) ...@@ -3583,9 +4292,12 @@ float8_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
if (isinf(val)) if (isinf(val))
ereport(ERROR, {
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), if (val < 0)
errmsg("cannot convert infinity to numeric"))); PG_RETURN_NUMERIC(make_result(&const_ninf));
else
PG_RETURN_NUMERIC(make_result(&const_pinf));
}
snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val); snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val);
...@@ -3609,8 +4321,15 @@ numeric_float8(PG_FUNCTION_ARGS) ...@@ -3609,8 +4321,15 @@ numeric_float8(PG_FUNCTION_ARGS)
char *tmp; char *tmp;
Datum result; Datum result;
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_FLOAT8(get_float8_nan()); {
if (NUMERIC_IS_PINF(num))
PG_RETURN_FLOAT8(get_float8_infinity());
else if (NUMERIC_IS_NINF(num))
PG_RETURN_FLOAT8(-get_float8_infinity());
else
PG_RETURN_FLOAT8(get_float8_nan());
}
tmp = DatumGetCString(DirectFunctionCall1(numeric_out, tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num))); NumericGetDatum(num)));
...@@ -3634,10 +4353,22 @@ numeric_float8_no_overflow(PG_FUNCTION_ARGS) ...@@ -3634,10 +4353,22 @@ numeric_float8_no_overflow(PG_FUNCTION_ARGS)
Numeric num = PG_GETARG_NUMERIC(0); Numeric num = PG_GETARG_NUMERIC(0);
double val; double val;
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_FLOAT8(get_float8_nan()); {
if (NUMERIC_IS_PINF(num))
val = HUGE_VAL;
else if (NUMERIC_IS_NINF(num))
val = -HUGE_VAL;
else
val = get_float8_nan();
}
else
{
NumericVar x;
val = numeric_to_double_no_overflow(num); init_var_from_num(num, &x);
val = numericvar_to_double_no_overflow(&x);
}
PG_RETURN_FLOAT8(val); PG_RETURN_FLOAT8(val);
} }
...@@ -3654,9 +4385,12 @@ float4_numeric(PG_FUNCTION_ARGS) ...@@ -3654,9 +4385,12 @@ float4_numeric(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
if (isinf(val)) if (isinf(val))
ereport(ERROR, {
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), if (val < 0)
errmsg("cannot convert infinity to numeric"))); PG_RETURN_NUMERIC(make_result(&const_ninf));
else
PG_RETURN_NUMERIC(make_result(&const_pinf));
}
snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val); snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val);
...@@ -3680,8 +4414,15 @@ numeric_float4(PG_FUNCTION_ARGS) ...@@ -3680,8 +4414,15 @@ numeric_float4(PG_FUNCTION_ARGS)
char *tmp; char *tmp;
Datum result; Datum result;
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_FLOAT4(get_float4_nan()); {
if (NUMERIC_IS_PINF(num))
PG_RETURN_FLOAT4(get_float4_infinity());
else if (NUMERIC_IS_NINF(num))
PG_RETURN_FLOAT4(-get_float4_infinity());
else
PG_RETURN_FLOAT4(get_float4_nan());
}
tmp = DatumGetCString(DirectFunctionCall1(numeric_out, tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num))); NumericGetDatum(num)));
...@@ -3701,10 +4442,17 @@ numeric_pg_lsn(PG_FUNCTION_ARGS) ...@@ -3701,10 +4442,17 @@ numeric_pg_lsn(PG_FUNCTION_ARGS)
NumericVar x; NumericVar x;
XLogRecPtr result; XLogRecPtr result;
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_SPECIAL(num))
ereport(ERROR, {
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), if (NUMERIC_IS_NAN(num))
errmsg("cannot convert NaN to pg_lsn"))); ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to pg_lsn")));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to pg_lsn")));
}
/* Convert to variable format and thence to pg_lsn */ /* Convert to variable format and thence to pg_lsn */
init_var_from_num(num, &x); init_var_from_num(num, &x);
...@@ -3741,9 +4489,15 @@ typedef struct NumericAggState ...@@ -3741,9 +4489,15 @@ typedef struct NumericAggState
NumericSumAccum sumX2; /* sum of squares of processed numbers */ NumericSumAccum sumX2; /* sum of squares of processed numbers */
int maxScale; /* maximum scale seen so far */ int maxScale; /* maximum scale seen so far */
int64 maxScaleCount; /* number of values seen with maximum scale */ int64 maxScaleCount; /* number of values seen with maximum scale */
int64 NaNcount; /* count of NaN values (not included in N!) */ /* These counts are *not* included in N! Use NA_TOTAL_COUNT() as needed */
int64 NaNcount; /* count of NaN values */
int64 pInfcount; /* count of +Inf values */
int64 nInfcount; /* count of -Inf values */
} NumericAggState; } NumericAggState;
#define NA_TOTAL_COUNT(na) \
((na)->N + (na)->NaNcount + (na)->pInfcount + (na)->nInfcount)
/* /*
* Prepare state data for a numeric aggregate function that needs to compute * Prepare state data for a numeric aggregate function that needs to compute
* sum, count and optionally sum of squares of the input. * sum, count and optionally sum of squares of the input.
...@@ -3795,10 +4549,15 @@ do_numeric_accum(NumericAggState *state, Numeric newval) ...@@ -3795,10 +4549,15 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
NumericVar X2; NumericVar X2;
MemoryContext old_context; MemoryContext old_context;
/* Count NaN inputs separately from all else */ /* Count NaN/infinity inputs separately from all else */
if (NUMERIC_IS_NAN(newval)) if (NUMERIC_IS_SPECIAL(newval))
{ {
state->NaNcount++; if (NUMERIC_IS_PINF(newval))
state->pInfcount++;
else if (NUMERIC_IS_NINF(newval))
state->nInfcount++;
else
state->NaNcount++;
return; return;
} }
...@@ -3860,10 +4619,15 @@ do_numeric_discard(NumericAggState *state, Numeric newval) ...@@ -3860,10 +4619,15 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
NumericVar X2; NumericVar X2;
MemoryContext old_context; MemoryContext old_context;
/* Count NaN inputs separately from all else */ /* Count NaN/infinity inputs separately from all else */
if (NUMERIC_IS_NAN(newval)) if (NUMERIC_IS_SPECIAL(newval))
{ {
state->NaNcount--; if (NUMERIC_IS_PINF(newval))
state->pInfcount--;
else if (NUMERIC_IS_NINF(newval))
state->nInfcount--;
else
state->NaNcount--;
return true; return true;
} }
...@@ -3986,6 +4750,8 @@ numeric_combine(PG_FUNCTION_ARGS) ...@@ -3986,6 +4750,8 @@ numeric_combine(PG_FUNCTION_ARGS)
state1 = makeNumericAggStateCurrentContext(true); state1 = makeNumericAggStateCurrentContext(true);
state1->N = state2->N; state1->N = state2->N;
state1->NaNcount = state2->NaNcount; state1->NaNcount = state2->NaNcount;
state1->pInfcount = state2->pInfcount;
state1->nInfcount = state2->nInfcount;
state1->maxScale = state2->maxScale; state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount; state1->maxScaleCount = state2->maxScaleCount;
...@@ -3999,6 +4765,8 @@ numeric_combine(PG_FUNCTION_ARGS) ...@@ -3999,6 +4765,8 @@ numeric_combine(PG_FUNCTION_ARGS)
state1->N += state2->N; state1->N += state2->N;
state1->NaNcount += state2->NaNcount; state1->NaNcount += state2->NaNcount;
state1->pInfcount += state2->pInfcount;
state1->nInfcount += state2->nInfcount;
if (state2->N > 0) if (state2->N > 0)
{ {
...@@ -4074,6 +4842,8 @@ numeric_avg_combine(PG_FUNCTION_ARGS) ...@@ -4074,6 +4842,8 @@ numeric_avg_combine(PG_FUNCTION_ARGS)
state1 = makeNumericAggStateCurrentContext(false); state1 = makeNumericAggStateCurrentContext(false);
state1->N = state2->N; state1->N = state2->N;
state1->NaNcount = state2->NaNcount; state1->NaNcount = state2->NaNcount;
state1->pInfcount = state2->pInfcount;
state1->nInfcount = state2->nInfcount;
state1->maxScale = state2->maxScale; state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount; state1->maxScaleCount = state2->maxScaleCount;
...@@ -4086,6 +4856,8 @@ numeric_avg_combine(PG_FUNCTION_ARGS) ...@@ -4086,6 +4856,8 @@ numeric_avg_combine(PG_FUNCTION_ARGS)
state1->N += state2->N; state1->N += state2->N;
state1->NaNcount += state2->NaNcount; state1->NaNcount += state2->NaNcount;
state1->pInfcount += state2->pInfcount;
state1->nInfcount += state2->nInfcount;
if (state2->N > 0) if (state2->N > 0)
{ {
...@@ -4164,6 +4936,12 @@ numeric_avg_serialize(PG_FUNCTION_ARGS) ...@@ -4164,6 +4936,12 @@ numeric_avg_serialize(PG_FUNCTION_ARGS)
/* NaNcount */ /* NaNcount */
pq_sendint64(&buf, state->NaNcount); pq_sendint64(&buf, state->NaNcount);
/* pInfcount */
pq_sendint64(&buf, state->pInfcount);
/* nInfcount */
pq_sendint64(&buf, state->nInfcount);
result = pq_endtypsend(&buf); result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result); PG_RETURN_BYTEA_P(result);
...@@ -4218,6 +4996,12 @@ numeric_avg_deserialize(PG_FUNCTION_ARGS) ...@@ -4218,6 +4996,12 @@ numeric_avg_deserialize(PG_FUNCTION_ARGS)
/* NaNcount */ /* NaNcount */
result->NaNcount = pq_getmsgint64(&buf); result->NaNcount = pq_getmsgint64(&buf);
/* pInfcount */
result->pInfcount = pq_getmsgint64(&buf);
/* nInfcount */
result->nInfcount = pq_getmsgint64(&buf);
pq_getmsgend(&buf); pq_getmsgend(&buf);
pfree(buf.data); pfree(buf.data);
...@@ -4286,6 +5070,12 @@ numeric_serialize(PG_FUNCTION_ARGS) ...@@ -4286,6 +5070,12 @@ numeric_serialize(PG_FUNCTION_ARGS)
/* NaNcount */ /* NaNcount */
pq_sendint64(&buf, state->NaNcount); pq_sendint64(&buf, state->NaNcount);
/* pInfcount */
pq_sendint64(&buf, state->pInfcount);
/* nInfcount */
pq_sendint64(&buf, state->nInfcount);
result = pq_endtypsend(&buf); result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result); PG_RETURN_BYTEA_P(result);
...@@ -4349,6 +5139,12 @@ numeric_deserialize(PG_FUNCTION_ARGS) ...@@ -4349,6 +5139,12 @@ numeric_deserialize(PG_FUNCTION_ARGS)
/* NaNcount */ /* NaNcount */
result->NaNcount = pq_getmsgint64(&buf); result->NaNcount = pq_getmsgint64(&buf);
/* pInfcount */
result->pInfcount = pq_getmsgint64(&buf);
/* nInfcount */
result->nInfcount = pq_getmsgint64(&buf);
pq_getmsgend(&buf); pq_getmsgend(&buf);
pfree(buf.data); pfree(buf.data);
...@@ -5141,12 +5937,20 @@ numeric_avg(PG_FUNCTION_ARGS) ...@@ -5141,12 +5937,20 @@ numeric_avg(PG_FUNCTION_ARGS)
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */ /* If there were no non-null inputs, return NULL */
if (state == NULL || (state->N + state->NaNcount) == 0) if (state == NULL || NA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL(); PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */ if (state->NaNcount > 0) /* there was at least one NaN input */
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* adding plus and minus infinities gives NaN */
if (state->pInfcount > 0 && state->nInfcount > 0)
PG_RETURN_NUMERIC(make_result(&const_nan));
if (state->pInfcount > 0)
PG_RETURN_NUMERIC(make_result(&const_pinf));
if (state->nInfcount > 0)
PG_RETURN_NUMERIC(make_result(&const_ninf));
N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N)); N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
init_var(&sumX_var); init_var(&sumX_var);
...@@ -5167,12 +5971,20 @@ numeric_sum(PG_FUNCTION_ARGS) ...@@ -5167,12 +5971,20 @@ numeric_sum(PG_FUNCTION_ARGS)
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */ /* If there were no non-null inputs, return NULL */
if (state == NULL || (state->N + state->NaNcount) == 0) if (state == NULL || NA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL(); PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */ if (state->NaNcount > 0) /* there was at least one NaN input */
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* adding plus and minus infinities gives NaN */
if (state->pInfcount > 0 && state->nInfcount > 0)
PG_RETURN_NUMERIC(make_result(&const_nan));
if (state->pInfcount > 0)
PG_RETURN_NUMERIC(make_result(&const_pinf));
if (state->nInfcount > 0)
PG_RETURN_NUMERIC(make_result(&const_ninf));
init_var(&sumX_var); init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var); accum_sum_final(&state->sumX, &sumX_var);
result = make_result(&sumX_var); result = make_result(&sumX_var);
...@@ -5208,9 +6020,9 @@ numeric_stddev_internal(NumericAggState *state, ...@@ -5208,9 +6020,9 @@ numeric_stddev_internal(NumericAggState *state,
/* /*
* Sample stddev and variance are undefined when N <= 1; population stddev * Sample stddev and variance are undefined when N <= 1; population stddev
* is undefined when N == 0. Return NULL in either case (note that NaNs * is undefined when N == 0. Return NULL in either case (note that NaNs
* count as normal inputs for this purpose). * and infinities count as normal inputs for this purpose).
*/ */
if (state == NULL || (totCount = state->N + state->NaNcount) == 0) if (state == NULL || (totCount = NA_TOTAL_COUNT(state)) == 0)
{ {
*is_null = true; *is_null = true;
return NULL; return NULL;
...@@ -5225,9 +6037,10 @@ numeric_stddev_internal(NumericAggState *state, ...@@ -5225,9 +6037,10 @@ numeric_stddev_internal(NumericAggState *state,
*is_null = false; *is_null = false;
/* /*
* Deal with NaN inputs. * Deal with NaN and infinity cases. By analogy to the behavior of the
* float8 functions, any infinity input produces NaN output.
*/ */
if (state->NaNcount > 0) if (state->NaNcount > 0 || state->pInfcount > 0 || state->nInfcount > 0)
return make_result(&const_nan); return make_result(&const_nan);
/* OK, normal calculation applies */ /* OK, normal calculation applies */
...@@ -5870,6 +6683,12 @@ dump_numeric(const char *str, Numeric num) ...@@ -5870,6 +6683,12 @@ dump_numeric(const char *str, Numeric num)
case NUMERIC_NAN: case NUMERIC_NAN:
printf("NaN"); printf("NaN");
break; break;
case NUMERIC_PINF:
printf("Infinity");
break;
case NUMERIC_NINF:
printf("-Infinity");
break;
default: default:
printf("SIGN=0x%x", NUMERIC_SIGN(num)); printf("SIGN=0x%x", NUMERIC_SIGN(num));
break; break;
...@@ -5901,6 +6720,12 @@ dump_var(const char *str, NumericVar *var) ...@@ -5901,6 +6720,12 @@ dump_var(const char *str, NumericVar *var)
case NUMERIC_NAN: case NUMERIC_NAN:
printf("NaN"); printf("NaN");
break; break;
case NUMERIC_PINF:
printf("Infinity");
break;
case NUMERIC_NINF:
printf("-Infinity");
break;
default: default:
printf("SIGN=0x%x", var->sign); printf("SIGN=0x%x", var->sign);
break; break;
...@@ -5918,8 +6743,9 @@ dump_var(const char *str, NumericVar *var) ...@@ -5918,8 +6743,9 @@ dump_var(const char *str, NumericVar *var)
* *
* Local functions follow * Local functions follow
* *
* In general, these do not support NaNs --- callers must eliminate * In general, these do not support "special" (NaN or infinity) inputs;
* the possibility of NaN first. (make_result() is an exception.) * callers should handle those possibilities first.
* (There are one or two exceptions, noted in their header comments.)
* *
* ---------------------------------------------------------------------- * ----------------------------------------------------------------------
*/ */
...@@ -5979,9 +6805,9 @@ zero_var(NumericVar *var) ...@@ -5979,9 +6805,9 @@ zero_var(NumericVar *var)
* *
* Parse a string and put the number into a variable * Parse a string and put the number into a variable
* *
* This function does not handle leading or trailing spaces, and it doesn't * This function does not handle leading or trailing spaces. It returns
* accept "NaN" either. It returns the end+1 position so that caller can * the end+1 position parsed, so that caller can check for trailing
* check for trailing spaces/garbage if deemed necessary. * spaces/garbage if deemed necessary.
* *
* cp is the place to actually start parsing; str is what to use in error * cp is the place to actually start parsing; str is what to use in error
* reports. (Typically cp would be the same except advanced over spaces.) * reports. (Typically cp would be the same except advanced over spaces.)
...@@ -6455,13 +7281,29 @@ get_str_from_var_sci(const NumericVar *var, int rscale) ...@@ -6455,13 +7281,29 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
} }
/*
* duplicate_numeric() - copy a packed-format Numeric
*
* This will handle NaN and Infinity cases.
*/
static Numeric
duplicate_numeric(Numeric num)
{
Numeric res;
res = (Numeric) palloc(VARSIZE(num));
memcpy(res, num, VARSIZE(num));
return res;
}
/* /*
* make_result_opt_error() - * make_result_opt_error() -
* *
* Create the packed db numeric format in palloc()'d memory from * Create the packed db numeric format in palloc()'d memory from
* a variable. If "*have_error" flag is provided, on error it's set to * a variable. This will handle NaN and Infinity cases.
* true, NULL returned. This is helpful when caller need to handle errors *
* by itself. * If "have_error" isn't NULL, on overflow *have_error is set to true and
* NULL is returned. This is helpful when caller needs to handle errors.
*/ */
static Numeric static Numeric
make_result_opt_error(const NumericVar *var, bool *have_error) make_result_opt_error(const NumericVar *var, bool *have_error)
...@@ -6476,12 +7318,22 @@ make_result_opt_error(const NumericVar *var, bool *have_error) ...@@ -6476,12 +7318,22 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
if (have_error) if (have_error)
*have_error = false; *have_error = false;
if (sign == NUMERIC_NAN) if ((sign & NUMERIC_SIGN_MASK) == NUMERIC_SPECIAL)
{ {
/*
* Verify valid special value. This could be just an Assert, perhaps,
* but it seems worthwhile to expend a few cycles to ensure that we
* never write any nonzero reserved bits to disk.
*/
if (!(sign == NUMERIC_NAN ||
sign == NUMERIC_PINF ||
sign == NUMERIC_NINF))
elog(ERROR, "invalid numeric sign value 0x%x", sign);
result = (Numeric) palloc(NUMERIC_HDRSZ_SHORT); result = (Numeric) palloc(NUMERIC_HDRSZ_SHORT);
SET_VARSIZE(result, NUMERIC_HDRSZ_SHORT); SET_VARSIZE(result, NUMERIC_HDRSZ_SHORT);
result->choice.n_header = NUMERIC_NAN; result->choice.n_header = sign;
/* the header word is all we need */ /* the header word is all we need */
dump_numeric("make_result()", result); dump_numeric("make_result()", result);
...@@ -6572,8 +7424,8 @@ make_result(const NumericVar *var) ...@@ -6572,8 +7424,8 @@ make_result(const NumericVar *var)
/* /*
* apply_typmod() - * apply_typmod() -
* *
* Do bounds checking and rounding according to the attributes * Do bounds checking and rounding according to the specified typmod.
* typmod field. * Note that this is only applied to normal finite values.
*/ */
static void static void
apply_typmod(NumericVar *var, int32 typmod) apply_typmod(NumericVar *var, int32 typmod)
...@@ -6646,6 +7498,45 @@ apply_typmod(NumericVar *var, int32 typmod) ...@@ -6646,6 +7498,45 @@ apply_typmod(NumericVar *var, int32 typmod)
} }
} }
/*
* apply_typmod_special() -
*
* Do bounds checking according to the specified typmod, for an Inf or NaN.
* For convenience of most callers, the value is presented in packed form.
*/
static void
apply_typmod_special(Numeric num, int32 typmod)
{
int precision;
int scale;
Assert(NUMERIC_IS_SPECIAL(num)); /* caller error if not */
/*
* NaN is allowed regardless of the typmod; that's rather dubious perhaps,
* but it's a longstanding behavior. Inf is rejected if we have any
* typmod restriction, since an infinity shouldn't be claimed to fit in
* any finite number of digits.
*/
if (NUMERIC_IS_NAN(num))
return;
/* Do nothing if we have a default typmod (-1) */
if (typmod < (int32) (VARHDRSZ))
return;
typmod -= VARHDRSZ;
precision = (typmod >> 16) & 0xffff;
scale = typmod & 0xffff;
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
errdetail("A field with precision %d, scale %d cannot hold an infinite value.",
precision, scale)));
}
/* /*
* Convert numeric to int8, rounding if needed. * Convert numeric to int8, rounding if needed.
* *
...@@ -6961,36 +7852,9 @@ int128_to_numericvar(int128 val, NumericVar *var) ...@@ -6961,36 +7852,9 @@ int128_to_numericvar(int128 val, NumericVar *var)
#endif #endif
/* /*
* Convert numeric to float8; if out of range, return +/- HUGE_VAL * Convert a NumericVar to float8; if out of range, return +/- HUGE_VAL
*/ */
static double static double
numeric_to_double_no_overflow(Numeric num)
{
char *tmp;
double val;
char *endptr;
tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
/* unlike float8in, we ignore ERANGE from strtod */
val = strtod(tmp, &endptr);
if (*endptr != '\0')
{
/* shouldn't happen ... */
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"double precision", tmp)));
}
pfree(tmp);
return val;
}
/* As above, but work from a NumericVar */
static double
numericvar_to_double_no_overflow(const NumericVar *var) numericvar_to_double_no_overflow(const NumericVar *var)
{ {
char *tmp; char *tmp;
......
...@@ -57,6 +57,7 @@ typedef struct NumericData *Numeric; ...@@ -57,6 +57,7 @@ typedef struct NumericData *Numeric;
* Utility functions in numeric.c * Utility functions in numeric.c
*/ */
extern bool numeric_is_nan(Numeric num); extern bool numeric_is_nan(Numeric num);
extern bool numeric_is_inf(Numeric num);
int32 numeric_maximum_size(int32 typmod); int32 numeric_maximum_size(int32 typmod);
extern char *numeric_out_sci(Numeric num, int scale); extern char *numeric_out_sci(Numeric num, int scale);
extern char *numeric_normalize(Numeric num); extern char *numeric_normalize(Numeric num);
......
...@@ -211,6 +211,18 @@ SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric); ...@@ -211,6 +211,18 @@ SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric);
0 | 0 |
(1 row) (1 row)
SELECT var_pop('inf'::numeric), var_samp('inf'::numeric);
var_pop | var_samp
---------+----------
NaN |
(1 row)
SELECT stddev_pop('inf'::numeric), stddev_samp('inf'::numeric);
stddev_pop | stddev_samp
------------+-------------
NaN |
(1 row)
SELECT var_pop('nan'::numeric), var_samp('nan'::numeric); SELECT var_pop('nan'::numeric), var_samp('nan'::numeric);
var_pop | var_samp var_pop | var_samp
---------+---------- ---------+----------
...@@ -285,32 +297,74 @@ select avg('NaN'::numeric) from generate_series(1,3); ...@@ -285,32 +297,74 @@ select avg('NaN'::numeric) from generate_series(1,3);
(1 row) (1 row)
-- verify correct results for infinite inputs -- verify correct results for infinite inputs
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('1'), ('infinity')) v(x); FROM (VALUES ('1'), ('infinity')) v(x);
avg | var_pop sum | avg | var_pop
----------+--------- ----------+----------+---------
Infinity | NaN Infinity | Infinity | NaN
(1 row) (1 row)
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('infinity'), ('1')) v(x); FROM (VALUES ('infinity'), ('1')) v(x);
avg | var_pop sum | avg | var_pop
----------+--------- ----------+----------+---------
Infinity | NaN Infinity | Infinity | NaN
(1 row) (1 row)
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('infinity'), ('infinity')) v(x); FROM (VALUES ('infinity'), ('infinity')) v(x);
avg | var_pop sum | avg | var_pop
----------+--------- ----------+----------+---------
Infinity | NaN Infinity | Infinity | NaN
(1 row) (1 row)
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('-infinity'), ('infinity')) v(x);
sum | avg | var_pop
-----+-----+---------
NaN | NaN | NaN
(1 row)
SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('-infinity'), ('-infinity')) v(x);
sum | avg | var_pop
-----------+-----------+---------
-Infinity | -Infinity | NaN
(1 row)
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('1'), ('infinity')) v(x);
sum | avg | var_pop
----------+----------+---------
Infinity | Infinity | NaN
(1 row)
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('infinity'), ('1')) v(x);
sum | avg | var_pop
----------+----------+---------
Infinity | Infinity | NaN
(1 row)
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('infinity'), ('infinity')) v(x);
sum | avg | var_pop
----------+----------+---------
Infinity | Infinity | NaN
(1 row)
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('-infinity'), ('infinity')) v(x); FROM (VALUES ('-infinity'), ('infinity')) v(x);
avg | var_pop sum | avg | var_pop
-----+--------- -----+-----+---------
NaN | NaN NaN | NaN | NaN
(1 row)
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('-infinity'), ('-infinity')) v(x);
sum | avg | var_pop
-----------+-----------+---------
-Infinity | -Infinity | NaN
(1 row) (1 row)
-- test accuracy with a large input offset -- test accuracy with a large input offset
......
...@@ -660,6 +660,432 @@ SELECT t1.id1, t1.result, t2.expected ...@@ -660,6 +660,432 @@ SELECT t1.id1, t1.result, t2.expected
-----+--------+---------- -----+--------+----------
(0 rows) (0 rows)
-- ******************************
-- * Check behavior with Inf and NaN inputs. It's easiest to handle these
-- * separately from the num_data framework used above, because some input
-- * combinations will throw errors.
-- ******************************
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
SELECT x1, x2,
x1 + x2 AS sum,
x1 - x2 AS diff,
x1 * x2 AS prod
FROM v AS v1(x1), v AS v2(x2);
x1 | x2 | sum | diff | prod
-----------+-----------+-----------+-----------+-----------
0 | 0 | 0 | 0 | 0
0 | 1 | 1 | -1 | 0
0 | -1 | -1 | 1 | 0
0 | 4.2 | 4.2 | -4.2 | 0.0
0 | Infinity | Infinity | -Infinity | NaN
0 | -Infinity | -Infinity | Infinity | NaN
0 | NaN | NaN | NaN | NaN
1 | 0 | 1 | 1 | 0
1 | 1 | 2 | 0 | 1
1 | -1 | 0 | 2 | -1
1 | 4.2 | 5.2 | -3.2 | 4.2
1 | Infinity | Infinity | -Infinity | Infinity
1 | -Infinity | -Infinity | Infinity | -Infinity
1 | NaN | NaN | NaN | NaN
-1 | 0 | -1 | -1 | 0
-1 | 1 | 0 | -2 | -1
-1 | -1 | -2 | 0 | 1
-1 | 4.2 | 3.2 | -5.2 | -4.2
-1 | Infinity | Infinity | -Infinity | -Infinity
-1 | -Infinity | -Infinity | Infinity | Infinity
-1 | NaN | NaN | NaN | NaN
4.2 | 0 | 4.2 | 4.2 | 0.0
4.2 | 1 | 5.2 | 3.2 | 4.2
4.2 | -1 | 3.2 | 5.2 | -4.2
4.2 | 4.2 | 8.4 | 0.0 | 17.64
4.2 | Infinity | Infinity | -Infinity | Infinity
4.2 | -Infinity | -Infinity | Infinity | -Infinity
4.2 | NaN | NaN | NaN | NaN
Infinity | 0 | Infinity | Infinity | NaN
Infinity | 1 | Infinity | Infinity | Infinity
Infinity | -1 | Infinity | Infinity | -Infinity
Infinity | 4.2 | Infinity | Infinity | Infinity
Infinity | Infinity | Infinity | NaN | Infinity
Infinity | -Infinity | NaN | Infinity | -Infinity
Infinity | NaN | NaN | NaN | NaN
-Infinity | 0 | -Infinity | -Infinity | NaN
-Infinity | 1 | -Infinity | -Infinity | -Infinity
-Infinity | -1 | -Infinity | -Infinity | Infinity
-Infinity | 4.2 | -Infinity | -Infinity | -Infinity
-Infinity | Infinity | NaN | -Infinity | -Infinity
-Infinity | -Infinity | -Infinity | NaN | Infinity
-Infinity | NaN | NaN | NaN | NaN
NaN | 0 | NaN | NaN | NaN
NaN | 1 | NaN | NaN | NaN
NaN | -1 | NaN | NaN | NaN
NaN | 4.2 | NaN | NaN | NaN
NaN | Infinity | NaN | NaN | NaN
NaN | -Infinity | NaN | NaN | NaN
NaN | NaN | NaN | NaN | NaN
(49 rows)
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
SELECT x1, x2,
x1 / x2 AS quot,
x1 % x2 AS mod,
div(x1, x2) AS div
FROM v AS v1(x1), v AS v2(x2) WHERE x2 != 0;
x1 | x2 | quot | mod | div
-----------+-----------+-------------------------+------+-----------
0 | 1 | 0.00000000000000000000 | 0 | 0
1 | 1 | 1.00000000000000000000 | 0 | 1
-1 | 1 | -1.00000000000000000000 | 0 | -1
4.2 | 1 | 4.2000000000000000 | 0.2 | 4
Infinity | 1 | Infinity | NaN | Infinity
-Infinity | 1 | -Infinity | NaN | -Infinity
NaN | 1 | NaN | NaN | NaN
0 | -1 | 0.00000000000000000000 | 0 | 0
1 | -1 | -1.00000000000000000000 | 0 | -1
-1 | -1 | 1.00000000000000000000 | 0 | 1
4.2 | -1 | -4.2000000000000000 | 0.2 | -4
Infinity | -1 | -Infinity | NaN | -Infinity
-Infinity | -1 | Infinity | NaN | Infinity
NaN | -1 | NaN | NaN | NaN
0 | 4.2 | 0.00000000000000000000 | 0.0 | 0
1 | 4.2 | 0.23809523809523809524 | 1.0 | 0
-1 | 4.2 | -0.23809523809523809524 | -1.0 | 0
4.2 | 4.2 | 1.00000000000000000000 | 0.0 | 1
Infinity | 4.2 | Infinity | NaN | Infinity
-Infinity | 4.2 | -Infinity | NaN | -Infinity
NaN | 4.2 | NaN | NaN | NaN
0 | Infinity | 0 | 0 | 0
1 | Infinity | 0 | 1 | 0
-1 | Infinity | 0 | -1 | 0
4.2 | Infinity | 0 | 4.2 | 0
Infinity | Infinity | NaN | NaN | NaN
-Infinity | Infinity | NaN | NaN | NaN
NaN | Infinity | NaN | NaN | NaN
0 | -Infinity | 0 | 0 | 0
1 | -Infinity | 0 | 1 | 0
-1 | -Infinity | 0 | -1 | 0
4.2 | -Infinity | 0 | 4.2 | 0
Infinity | -Infinity | NaN | NaN | NaN
-Infinity | -Infinity | NaN | NaN | NaN
NaN | -Infinity | NaN | NaN | NaN
0 | NaN | NaN | NaN | NaN
1 | NaN | NaN | NaN | NaN
-1 | NaN | NaN | NaN | NaN
4.2 | NaN | NaN | NaN | NaN
Infinity | NaN | NaN | NaN | NaN
-Infinity | NaN | NaN | NaN | NaN
NaN | NaN | NaN | NaN | NaN
(42 rows)
SELECT 'inf'::numeric / '0';
ERROR: division by zero
SELECT '-inf'::numeric / '0';
ERROR: division by zero
SELECT 'nan'::numeric / '0';
?column?
----------
NaN
(1 row)
SELECT '0'::numeric / '0';
ERROR: division by zero
SELECT 'inf'::numeric % '0';
ERROR: division by zero
SELECT '-inf'::numeric % '0';
ERROR: division by zero
SELECT 'nan'::numeric % '0';
?column?
----------
NaN
(1 row)
SELECT '0'::numeric % '0';
ERROR: division by zero
SELECT div('inf'::numeric, '0');
ERROR: division by zero
SELECT div('-inf'::numeric, '0');
ERROR: division by zero
SELECT div('nan'::numeric, '0');
div
-----
NaN
(1 row)
SELECT div('0'::numeric, '0');
ERROR: division by zero
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
SELECT x, -x as minusx, abs(x), floor(x), ceil(x), sign(x), numeric_inc(x) as inc
FROM v;
x | minusx | abs | floor | ceil | sign | inc
-----------+-----------+----------+-----------+-----------+------+-----------
0 | 0 | 0 | 0 | 0 | 0 | 1
1 | -1 | 1 | 1 | 1 | 1 | 2
-1 | 1 | 1 | -1 | -1 | -1 | 0
4.2 | -4.2 | 4.2 | 4 | 5 | 1 | 5.2
-7.777 | 7.777 | 7.777 | -8 | -7 | -1 | -6.777
Infinity | -Infinity | Infinity | Infinity | Infinity | 1 | Infinity
-Infinity | Infinity | Infinity | -Infinity | -Infinity | -1 | -Infinity
NaN | NaN | NaN | NaN | NaN | NaN | NaN
(8 rows)
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
SELECT x, round(x), round(x,1) as round1, trunc(x), trunc(x,1) as trunc1
FROM v;
x | round | round1 | trunc | trunc1
-----------+-----------+-----------+-----------+-----------
0 | 0 | 0.0 | 0 | 0.0
1 | 1 | 1.0 | 1 | 1.0
-1 | -1 | -1.0 | -1 | -1.0
4.2 | 4 | 4.2 | 4 | 4.2
-7.777 | -8 | -7.8 | -7 | -7.7
Infinity | Infinity | Infinity | Infinity | Infinity
-Infinity | -Infinity | -Infinity | -Infinity | -Infinity
NaN | NaN | NaN | NaN | NaN
(8 rows)
-- the large values fall into the numeric abbreviation code's maximal classes
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('1e340'),('-1e340'),
('inf'),('-inf'),('nan'),
('inf'),('-inf'),('nan'))
SELECT substring(x::text, 1, 32)
FROM v ORDER BY x;
substring
----------------------------------
-Infinity
-Infinity
-1000000000000000000000000000000
-7.777
-1
0
1
4.2
10000000000000000000000000000000
Infinity
Infinity
NaN
NaN
(13 rows)
WITH v(x) AS
(VALUES('0'::numeric),('1'),('4.2'),('inf'),('nan'))
SELECT x, sqrt(x)
FROM v;
x | sqrt
----------+-------------------
0 | 0.000000000000000
1 | 1.000000000000000
4.2 | 2.049390153191920
Infinity | Infinity
NaN | NaN
(5 rows)
SELECT sqrt('-1'::numeric);
ERROR: cannot take square root of a negative number
SELECT sqrt('-inf'::numeric);
ERROR: cannot take square root of a negative number
WITH v(x) AS
(VALUES('1'::numeric),('4.2'),('inf'),('nan'))
SELECT x,
log(x),
log10(x),
ln(x)
FROM v;
x | log | log10 | ln
----------+--------------------+--------------------+--------------------
1 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000
4.2 | 0.6232492903979005 | 0.6232492903979005 | 1.4350845252893226
Infinity | Infinity | Infinity | Infinity
NaN | NaN | NaN | NaN
(4 rows)
SELECT ln('0'::numeric);
ERROR: cannot take logarithm of zero
SELECT ln('-1'::numeric);
ERROR: cannot take logarithm of a negative number
SELECT ln('-inf'::numeric);
ERROR: cannot take logarithm of a negative number
WITH v(x) AS
(VALUES('2'::numeric),('4.2'),('inf'),('nan'))
SELECT x1, x2,
log(x1, x2)
FROM v AS v1(x1), v AS v2(x2);
x1 | x2 | log
----------+----------+--------------------
2 | 2 | 1.0000000000000000
2 | 4.2 | 2.0703893278913979
2 | Infinity | Infinity
2 | NaN | NaN
4.2 | 2 | 0.4830009440873890
4.2 | 4.2 | 1.0000000000000000
4.2 | Infinity | Infinity
4.2 | NaN | NaN
Infinity | 2 | 0
Infinity | 4.2 | 0
Infinity | Infinity | NaN
Infinity | NaN | NaN
NaN | 2 | NaN
NaN | 4.2 | NaN
NaN | Infinity | NaN
NaN | NaN | NaN
(16 rows)
SELECT log('0'::numeric, '10');
ERROR: cannot take logarithm of zero
SELECT log('10'::numeric, '0');
ERROR: cannot take logarithm of zero
SELECT log('-inf'::numeric, '10');
ERROR: cannot take logarithm of a negative number
SELECT log('10'::numeric, '-inf');
ERROR: cannot take logarithm of a negative number
SELECT log('inf'::numeric, '0');
ERROR: cannot take logarithm of zero
SELECT log('inf'::numeric, '-inf');
ERROR: cannot take logarithm of a negative number
SELECT log('-inf'::numeric, 'inf');
ERROR: cannot take logarithm of a negative number
WITH v(x) AS
(VALUES('0'::numeric),('1'),('2'),('4.2'),('inf'),('nan'))
SELECT x1, x2,
power(x1, x2)
FROM v AS v1(x1), v AS v2(x2) WHERE x1 != 0 OR x2 >= 0;
x1 | x2 | power
----------+----------+---------------------
0 | 0 | 1.0000000000000000
0 | 1 | 0.0000000000000000
0 | 2 | 0.0000000000000000
0 | 4.2 | 0.0000000000000000
0 | Infinity | 0
0 | NaN | NaN
1 | 0 | 1.0000000000000000
1 | 1 | 1.0000000000000000
1 | 2 | 1.0000000000000000
1 | 4.2 | 1.0000000000000000
1 | Infinity | 1
1 | NaN | 1
2 | 0 | 1.0000000000000000
2 | 1 | 2.0000000000000000
2 | 2 | 4.0000000000000000
2 | 4.2 | 18.379173679952560
2 | Infinity | Infinity
2 | NaN | NaN
4.2 | 0 | 1.0000000000000000
4.2 | 1 | 4.2000000000000000
4.2 | 2 | 17.6400000000000000
4.2 | 4.2 | 414.61691860129675
4.2 | Infinity | Infinity
4.2 | NaN | NaN
Infinity | 0 | 1
Infinity | 1 | Infinity
Infinity | 2 | Infinity
Infinity | 4.2 | Infinity
Infinity | Infinity | Infinity
Infinity | NaN | NaN
NaN | 0 | 1
NaN | 1 | NaN
NaN | 2 | NaN
NaN | 4.2 | NaN
NaN | Infinity | NaN
NaN | NaN | NaN
(36 rows)
SELECT power('0'::numeric, '-1');
ERROR: zero raised to a negative power is undefined
SELECT power('0'::numeric, '-inf');
ERROR: zero raised to a negative power is undefined
SELECT power('-1'::numeric, 'inf');
power
-------
1
(1 row)
SELECT power('-2'::numeric, '3');
power
---------------------
-8.0000000000000000
(1 row)
SELECT power('-2'::numeric, '3.3');
ERROR: a negative number raised to a non-integer power yields a complex result
SELECT power('-2'::numeric, '-1');
power
---------------------
-0.5000000000000000
(1 row)
SELECT power('-2'::numeric, '-1.5');
ERROR: a negative number raised to a non-integer power yields a complex result
SELECT power('-2'::numeric, 'inf');
power
----------
Infinity
(1 row)
SELECT power('-2'::numeric, '-inf');
power
-------
0
(1 row)
SELECT power('inf'::numeric, '-2');
power
-------
0
(1 row)
SELECT power('inf'::numeric, '-inf');
power
-------
0
(1 row)
SELECT power('-inf'::numeric, '2');
power
----------
Infinity
(1 row)
SELECT power('-inf'::numeric, '3');
power
-----------
-Infinity
(1 row)
SELECT power('-inf'::numeric, '4.5');
ERROR: a negative number raised to a non-integer power yields a complex result
SELECT power('-inf'::numeric, '-2');
power
-------
0
(1 row)
SELECT power('-inf'::numeric, '-3');
power
-------
0
(1 row)
SELECT power('-inf'::numeric, '0');
power
-------
1
(1 row)
SELECT power('-inf'::numeric, 'inf');
power
----------
Infinity
(1 row)
SELECT power('-inf'::numeric, '-inf');
power
-------
0
(1 row)
-- ****************************** -- ******************************
-- * miscellaneous checks for things that have been broken in the past... -- * miscellaneous checks for things that have been broken in the past...
-- ****************************** -- ******************************
...@@ -696,6 +1122,13 @@ ERROR: numeric field overflow ...@@ -696,6 +1122,13 @@ ERROR: numeric field overflow
DETAIL: A field with precision 4, scale 4 must round to an absolute value less than 1. DETAIL: A field with precision 4, scale 4 must round to an absolute value less than 1.
INSERT INTO fract_only VALUES (7, '0.00001'); INSERT INTO fract_only VALUES (7, '0.00001');
INSERT INTO fract_only VALUES (8, '0.00017'); INSERT INTO fract_only VALUES (8, '0.00017');
INSERT INTO fract_only VALUES (9, 'NaN');
INSERT INTO fract_only VALUES (10, 'Inf'); -- should fail
ERROR: numeric field overflow
DETAIL: A field with precision 4, scale 4 cannot hold an infinite value.
INSERT INTO fract_only VALUES (11, '-Inf'); -- should fail
ERROR: numeric field overflow
DETAIL: A field with precision 4, scale 4 cannot hold an infinite value.
SELECT * FROM fract_only; SELECT * FROM fract_only;
id | val id | val
----+--------- ----+---------
...@@ -705,7 +1138,8 @@ SELECT * FROM fract_only; ...@@ -705,7 +1138,8 @@ SELECT * FROM fract_only;
5 | 0.9999 5 | 0.9999
7 | 0.0000 7 | 0.0000
8 | 0.0002 8 | 0.0002
(6 rows) 9 | NaN
(7 rows)
DROP TABLE fract_only; DROP TABLE fract_only;
-- Check inf/nan conversion behavior -- Check inf/nan conversion behavior
...@@ -716,9 +1150,35 @@ SELECT 'NaN'::float8::numeric; ...@@ -716,9 +1150,35 @@ SELECT 'NaN'::float8::numeric;
(1 row) (1 row)
SELECT 'Infinity'::float8::numeric; SELECT 'Infinity'::float8::numeric;
ERROR: cannot convert infinity to numeric numeric
----------
Infinity
(1 row)
SELECT '-Infinity'::float8::numeric; SELECT '-Infinity'::float8::numeric;
ERROR: cannot convert infinity to numeric numeric
-----------
-Infinity
(1 row)
SELECT 'NaN'::numeric::float8;
float8
--------
NaN
(1 row)
SELECT 'Infinity'::numeric::float8;
float8
----------
Infinity
(1 row)
SELECT '-Infinity'::numeric::float8;
float8
-----------
-Infinity
(1 row)
SELECT 'NaN'::float4::numeric; SELECT 'NaN'::float4::numeric;
numeric numeric
--------- ---------
...@@ -726,9 +1186,59 @@ SELECT 'NaN'::float4::numeric; ...@@ -726,9 +1186,59 @@ SELECT 'NaN'::float4::numeric;
(1 row) (1 row)
SELECT 'Infinity'::float4::numeric; SELECT 'Infinity'::float4::numeric;
ERROR: cannot convert infinity to numeric numeric
----------
Infinity
(1 row)
SELECT '-Infinity'::float4::numeric; SELECT '-Infinity'::float4::numeric;
ERROR: cannot convert infinity to numeric numeric
-----------
-Infinity
(1 row)
SELECT 'NaN'::numeric::float4;
float4
--------
NaN
(1 row)
SELECT 'Infinity'::numeric::float4;
float4
----------
Infinity
(1 row)
SELECT '-Infinity'::numeric::float4;
float4
-----------
-Infinity
(1 row)
SELECT '42'::int2::numeric;
numeric
---------
42
(1 row)
SELECT 'NaN'::numeric::int2;
ERROR: cannot convert NaN to smallint
SELECT 'Infinity'::numeric::int2;
ERROR: cannot convert infinity to smallint
SELECT '-Infinity'::numeric::int2;
ERROR: cannot convert infinity to smallint
SELECT 'NaN'::numeric::int4;
ERROR: cannot convert NaN to integer
SELECT 'Infinity'::numeric::int4;
ERROR: cannot convert infinity to integer
SELECT '-Infinity'::numeric::int4;
ERROR: cannot convert infinity to integer
SELECT 'NaN'::numeric::int8;
ERROR: cannot convert NaN to bigint
SELECT 'Infinity'::numeric::int8;
ERROR: cannot convert infinity to bigint
SELECT '-Infinity'::numeric::int8;
ERROR: cannot convert infinity to bigint
-- Simple check that ceil(), floor(), and round() work correctly -- Simple check that ceil(), floor(), and round() work correctly
CREATE TABLE ceil_floor_round (a numeric); CREATE TABLE ceil_floor_round (a numeric);
INSERT INTO ceil_floor_round VALUES ('-5.5'); INSERT INTO ceil_floor_round VALUES ('-5.5');
...@@ -794,6 +1304,12 @@ SELECT width_bucket('NaN', 3.0, 4.0, 888); ...@@ -794,6 +1304,12 @@ SELECT width_bucket('NaN', 3.0, 4.0, 888);
ERROR: operand, lower bound, and upper bound cannot be NaN ERROR: operand, lower bound, and upper bound cannot be NaN
SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888); SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888);
ERROR: operand, lower bound, and upper bound cannot be NaN ERROR: operand, lower bound, and upper bound cannot be NaN
SELECT width_bucket('inf', 3.0, 4.0, 888);
ERROR: operand, lower bound, and upper bound cannot be infinity
SELECT width_bucket(2.0, 3.0, '-inf', 888);
ERROR: operand, lower bound, and upper bound cannot be infinity
SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888);
ERROR: lower and upper bounds must be finite
-- normal operation -- normal operation
CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8); CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8);
COPY width_bucket_test (operand_num) FROM stdin; COPY width_bucket_test (operand_num) FROM stdin;
...@@ -1199,6 +1715,60 @@ SELECT '' AS to_char_23, to_char(val, '9.999EEEE') FROM num_data; ...@@ -1199,6 +1715,60 @@ SELECT '' AS to_char_23, to_char(val, '9.999EEEE') FROM num_data;
| -2.493e+07 | -2.493e+07
(10 rows) (10 rows)
WITH v(val) AS
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
SELECT val,
to_char(val, '9.999EEEE') as numeric,
to_char(val::float8, '9.999EEEE') as float8,
to_char(val::float4, '9.999EEEE') as float4
FROM v;
val | numeric | float8 | float4
------------+------------+------------+------------
0 | 0.000e+00 | 0.000e+00 | 0.000e+00
-4.2 | -4.200e+00 | -4.200e+00 | -4.200e+00
4200000000 | 4.200e+09 | 4.200e+09 | 4.200e+09
0.000012 | 1.200e-05 | 1.200e-05 | 1.200e-05
Infinity | #.####### | #.####### | #.#######
-Infinity | #.####### | #.####### | #.#######
NaN | #.####### | #.####### | #.#######
(7 rows)
WITH v(val) AS
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
SELECT val,
to_char(val, 'MI9999999999.99') as numeric,
to_char(val::float8, 'MI9999999999.99') as float8,
to_char(val::float4, 'MI9999999999.99') as float4
FROM v;
val | numeric | float8 | float4
------------+----------------+----------------+----------------
0 | .00 | .00 | .00
-4.2 | - 4.20 | - 4.20 | - 4.20
4200000000 | 4200000000.00 | 4200000000.00 | 4200000000
0.000012 | .00 | .00 | .00
Infinity | Infinity | Infinity | Infinity
-Infinity | - Infinity | - Infinity | - Infinity
NaN | NaN | NaN | NaN
(7 rows)
WITH v(val) AS
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
SELECT val,
to_char(val, 'MI99.99') as numeric,
to_char(val::float8, 'MI99.99') as float8,
to_char(val::float4, 'MI99.99') as float4
FROM v;
val | numeric | float8 | float4
------------+---------+--------+--------
0 | .00 | .00 | .00
-4.2 | - 4.20 | - 4.20 | - 4.20
4200000000 | ##.## | ##.## | ##.
0.000012 | .00 | .00 | .00
Infinity | ##.## | ##.## | ##.
-Infinity | -##.## | -##.## | -##.
NaN | ##.## | ##.## | ##.##
(7 rows)
SELECT '' AS to_char_24, to_char('100'::numeric, 'FM999.9'); SELECT '' AS to_char_24, to_char('100'::numeric, 'FM999.9');
to_char_24 | to_char to_char_24 | to_char
------------+--------- ------------+---------
...@@ -1426,6 +1996,12 @@ INSERT INTO num_input_test(n1) VALUES ('555.50'); ...@@ -1426,6 +1996,12 @@ INSERT INTO num_input_test(n1) VALUES ('555.50');
INSERT INTO num_input_test(n1) VALUES ('-555.50'); INSERT INTO num_input_test(n1) VALUES ('-555.50');
INSERT INTO num_input_test(n1) VALUES ('NaN '); INSERT INTO num_input_test(n1) VALUES ('NaN ');
INSERT INTO num_input_test(n1) VALUES (' nan'); INSERT INTO num_input_test(n1) VALUES (' nan');
INSERT INTO num_input_test(n1) VALUES (' inf ');
INSERT INTO num_input_test(n1) VALUES (' +inf ');
INSERT INTO num_input_test(n1) VALUES (' -inf ');
INSERT INTO num_input_test(n1) VALUES (' Infinity ');
INSERT INTO num_input_test(n1) VALUES (' +inFinity ');
INSERT INTO num_input_test(n1) VALUES (' -INFINITY ');
-- bad inputs -- bad inputs
INSERT INTO num_input_test(n1) VALUES (' '); INSERT INTO num_input_test(n1) VALUES (' ');
ERROR: invalid input syntax for type numeric: " " ERROR: invalid input syntax for type numeric: " "
...@@ -1459,17 +2035,27 @@ INSERT INTO num_input_test(n1) VALUES (' N aN '); ...@@ -1459,17 +2035,27 @@ INSERT INTO num_input_test(n1) VALUES (' N aN ');
ERROR: invalid input syntax for type numeric: " N aN " ERROR: invalid input syntax for type numeric: " N aN "
LINE 1: INSERT INTO num_input_test(n1) VALUES (' N aN '); LINE 1: INSERT INTO num_input_test(n1) VALUES (' N aN ');
^ ^
INSERT INTO num_input_test(n1) VALUES ('+ infinity');
ERROR: invalid input syntax for type numeric: "+ infinity"
LINE 1: INSERT INTO num_input_test(n1) VALUES ('+ infinity');
^
SELECT * FROM num_input_test; SELECT * FROM num_input_test;
n1 n1
--------- -----------
123 123
3245874 3245874
-93853 -93853
555.50 555.50
-555.50 -555.50
NaN NaN
NaN NaN
(7 rows) Infinity
Infinity
-Infinity
Infinity
Infinity
-Infinity
(13 rows)
-- --
-- Test some corner cases for multiplication -- Test some corner cases for multiplication
...@@ -1805,6 +2391,24 @@ select exp(1.0::numeric(71,70)); ...@@ -1805,6 +2391,24 @@ select exp(1.0::numeric(71,70));
2.7182818284590452353602874713526624977572470936999595749669676277240766 2.7182818284590452353602874713526624977572470936999595749669676277240766
(1 row) (1 row)
select exp('nan'::numeric);
exp
-----
NaN
(1 row)
select exp('inf'::numeric);
exp
----------
Infinity
(1 row)
select exp('-inf'::numeric);
exp
-----
0
(1 row)
-- cases that used to generate inaccurate results -- cases that used to generate inaccurate results
select exp(32.999); select exp(32.999);
exp exp
...@@ -1876,6 +2480,12 @@ select * from generate_series('nan'::numeric, 100::numeric, 10::numeric); ...@@ -1876,6 +2480,12 @@ select * from generate_series('nan'::numeric, 100::numeric, 10::numeric);
ERROR: start value cannot be NaN ERROR: start value cannot be NaN
select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric); select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric);
ERROR: stop value cannot be NaN ERROR: stop value cannot be NaN
select * from generate_series('inf'::numeric, 'inf'::numeric, 10::numeric);
ERROR: start value cannot be infinity
select * from generate_series(0::numeric, 'inf'::numeric, 10::numeric);
ERROR: stop value cannot be infinity
select * from generate_series(0::numeric, '42'::numeric, '-inf'::numeric);
ERROR: step size cannot be infinity
-- Checks maximum, output is truncated -- Checks maximum, output is truncated
select (i / (10::numeric ^ 131071))::numeric(1,0) select (i / (10::numeric ^ 131071))::numeric(1,0)
from generate_series(6 * (10::numeric ^ 131071), from generate_series(6 * (10::numeric ^ 131071),
...@@ -2081,6 +2691,12 @@ select scale(numeric 'NaN'); ...@@ -2081,6 +2691,12 @@ select scale(numeric 'NaN');
(1 row) (1 row)
select scale(numeric 'inf');
scale
-------
(1 row)
select scale(NULL::numeric); select scale(NULL::numeric);
scale scale
------- -------
...@@ -2138,6 +2754,12 @@ select min_scale(numeric 'NaN') is NULL; -- should be true ...@@ -2138,6 +2754,12 @@ select min_scale(numeric 'NaN') is NULL; -- should be true
t t
(1 row) (1 row)
select min_scale(numeric 'inf') is NULL; -- should be true
?column?
----------
t
(1 row)
select min_scale(0); -- no digits select min_scale(0); -- no digits
min_scale min_scale
----------- -----------
...@@ -2207,6 +2829,12 @@ select trim_scale(numeric 'NaN'); ...@@ -2207,6 +2829,12 @@ select trim_scale(numeric 'NaN');
NaN NaN
(1 row) (1 row)
select trim_scale(numeric 'inf');
trim_scale
------------
Infinity
(1 row)
select trim_scale(1.120); select trim_scale(1.120);
trim_scale trim_scale
------------ ------------
...@@ -2280,7 +2908,11 @@ FROM (VALUES (0::numeric, 0::numeric), ...@@ -2280,7 +2908,11 @@ FROM (VALUES (0::numeric, 0::numeric),
(0::numeric, 46375::numeric), (0::numeric, 46375::numeric),
(433125::numeric, 46375::numeric), (433125::numeric, 46375::numeric),
(43312.5::numeric, 4637.5::numeric), (43312.5::numeric, 4637.5::numeric),
(4331.250::numeric, 463.75000::numeric)) AS v(a, b); (4331.250::numeric, 463.75000::numeric),
('inf', '0'),
('inf', '42'),
('inf', 'inf')
) AS v(a, b);
a | b | gcd | gcd | gcd | gcd a | b | gcd | gcd | gcd | gcd
----------+-----------+---------+---------+---------+--------- ----------+-----------+---------+---------+---------+---------
0 | 0 | 0 | 0 | 0 | 0 0 | 0 | 0 | 0 | 0 | 0
...@@ -2289,7 +2921,10 @@ FROM (VALUES (0::numeric, 0::numeric), ...@@ -2289,7 +2921,10 @@ FROM (VALUES (0::numeric, 0::numeric),
433125 | 46375 | 875 | 875 | 875 | 875 433125 | 46375 | 875 | 875 | 875 | 875
43312.5 | 4637.5 | 87.5 | 87.5 | 87.5 | 87.5 43312.5 | 4637.5 | 87.5 | 87.5 | 87.5 | 87.5
4331.250 | 463.75000 | 8.75000 | 8.75000 | 8.75000 | 8.75000 4331.250 | 463.75000 | 8.75000 | 8.75000 | 8.75000 | 8.75000
(6 rows) Infinity | 0 | NaN | NaN | NaN | NaN
Infinity | 42 | NaN | NaN | NaN | NaN
Infinity | Infinity | NaN | NaN | NaN | NaN
(9 rows)
-- --
-- Tests for LCM() -- Tests for LCM()
...@@ -2301,7 +2936,11 @@ FROM (VALUES (0::numeric, 0::numeric), ...@@ -2301,7 +2936,11 @@ FROM (VALUES (0::numeric, 0::numeric),
(13272::numeric, 13272::numeric), (13272::numeric, 13272::numeric),
(423282::numeric, 13272::numeric), (423282::numeric, 13272::numeric),
(42328.2::numeric, 1327.2::numeric), (42328.2::numeric, 1327.2::numeric),
(4232.820::numeric, 132.72000::numeric)) AS v(a, b); (4232.820::numeric, 132.72000::numeric),
('inf', '0'),
('inf', '42'),
('inf', 'inf')
) AS v(a, b);
a | b | lcm | lcm | lcm | lcm a | b | lcm | lcm | lcm | lcm
----------+-----------+--------------+--------------+--------------+-------------- ----------+-----------+--------------+--------------+--------------+--------------
0 | 0 | 0 | 0 | 0 | 0 0 | 0 | 0 | 0 | 0 | 0
...@@ -2311,7 +2950,10 @@ FROM (VALUES (0::numeric, 0::numeric), ...@@ -2311,7 +2950,10 @@ FROM (VALUES (0::numeric, 0::numeric),
423282 | 13272 | 11851896 | 11851896 | 11851896 | 11851896 423282 | 13272 | 11851896 | 11851896 | 11851896 | 11851896
42328.2 | 1327.2 | 1185189.6 | 1185189.6 | 1185189.6 | 1185189.6 42328.2 | 1327.2 | 1185189.6 | 1185189.6 | 1185189.6 | 1185189.6
4232.820 | 132.72000 | 118518.96000 | 118518.96000 | 118518.96000 | 118518.96000 4232.820 | 132.72000 | 118518.96000 | 118518.96000 | 118518.96000 | 118518.96000
(7 rows) Infinity | 0 | NaN | NaN | NaN | NaN
Infinity | 42 | NaN | NaN | NaN | NaN
Infinity | Infinity | NaN | NaN | NaN | NaN
(10 rows)
SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow
ERROR: value overflows numeric format ERROR: value overflows numeric format
......
...@@ -1872,7 +1872,7 @@ create temp table numerics( ...@@ -1872,7 +1872,7 @@ create temp table numerics(
f_numeric numeric f_numeric numeric
); );
insert into numerics values insert into numerics values
(0, '-infinity', '-infinity', '-1000'), -- numeric type lacks infinities (0, '-infinity', '-infinity', '-infinity'),
(1, -3, -3, -3), (1, -3, -3, -3),
(2, -1, -1, -1), (2, -1, -1, -1),
(3, 0, 0, 0), (3, 0, 0, 0),
...@@ -1880,7 +1880,7 @@ insert into numerics values ...@@ -1880,7 +1880,7 @@ insert into numerics values
(5, 1.12, 1.12, 1.12), (5, 1.12, 1.12, 1.12),
(6, 2, 2, 2), (6, 2, 2, 2),
(7, 100, 100, 100), (7, 100, 100, 100),
(8, 'infinity', 'infinity', '1000'), (8, 'infinity', 'infinity', 'infinity'),
(9, 'NaN', 'NaN', 'NaN'); (9, 'NaN', 'NaN', 'NaN');
select id, f_float4, first_value(id) over w, last_value(id) over w select id, f_float4, first_value(id) over w, last_value(id) over w
from numerics from numerics
...@@ -2078,7 +2078,7 @@ window w as (order by f_numeric range between ...@@ -2078,7 +2078,7 @@ window w as (order by f_numeric range between
1 preceding and 1 following); 1 preceding and 1 following);
id | f_numeric | first_value | last_value id | f_numeric | first_value | last_value
----+-----------+-------------+------------ ----+-----------+-------------+------------
0 | -1000 | 0 | 0 0 | -Infinity | 0 | 0
1 | -3 | 1 | 1 1 | -3 | 1 | 1
2 | -1 | 2 | 3 2 | -1 | 2 | 3
3 | 0 | 2 | 3 3 | 0 | 2 | 3
...@@ -2086,7 +2086,7 @@ window w as (order by f_numeric range between ...@@ -2086,7 +2086,7 @@ window w as (order by f_numeric range between
5 | 1.12 | 4 | 6 5 | 1.12 | 4 | 6
6 | 2 | 4 | 6 6 | 2 | 4 | 6
7 | 100 | 7 | 7 7 | 100 | 7 | 7
8 | 1000 | 8 | 8 8 | Infinity | 8 | 8
9 | NaN | 9 | 9 9 | NaN | 9 | 9
(10 rows) (10 rows)
...@@ -2096,7 +2096,7 @@ window w as (order by f_numeric range between ...@@ -2096,7 +2096,7 @@ window w as (order by f_numeric range between
1 preceding and 1.1::numeric following); 1 preceding and 1.1::numeric following);
id | f_numeric | first_value | last_value id | f_numeric | first_value | last_value
----+-----------+-------------+------------ ----+-----------+-------------+------------
0 | -1000 | 0 | 0 0 | -Infinity | 0 | 0
1 | -3 | 1 | 1 1 | -3 | 1 | 1
2 | -1 | 2 | 3 2 | -1 | 2 | 3
3 | 0 | 2 | 4 3 | 0 | 2 | 4
...@@ -2104,7 +2104,7 @@ window w as (order by f_numeric range between ...@@ -2104,7 +2104,7 @@ window w as (order by f_numeric range between
5 | 1.12 | 4 | 6 5 | 1.12 | 4 | 6
6 | 2 | 4 | 6 6 | 2 | 4 | 6
7 | 100 | 7 | 7 7 | 100 | 7 | 7
8 | 1000 | 8 | 8 8 | Infinity | 8 | 8
9 | NaN | 9 | 9 9 | NaN | 9 | 9
(10 rows) (10 rows)
...@@ -2116,6 +2116,60 @@ ERROR: RANGE with offset PRECEDING/FOLLOWING is not supported for column type n ...@@ -2116,6 +2116,60 @@ ERROR: RANGE with offset PRECEDING/FOLLOWING is not supported for column type n
LINE 4: 1 preceding and 1.1::float8 following); LINE 4: 1 preceding and 1.1::float8 following);
^ ^
HINT: Cast the offset value to an appropriate type. HINT: Cast the offset value to an appropriate type.
select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics
window w as (order by f_numeric range between
'inf' preceding and 'inf' following);
id | f_numeric | first_value | last_value
----+-----------+-------------+------------
0 | -Infinity | 0 | 8
1 | -3 | 0 | 8
2 | -1 | 0 | 8
3 | 0 | 0 | 8
4 | 1.1 | 0 | 8
5 | 1.12 | 0 | 8
6 | 2 | 0 | 8
7 | 100 | 0 | 8
8 | Infinity | 0 | 8
9 | NaN | 9 | 9
(10 rows)
select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics
window w as (order by f_numeric range between
'inf' preceding and 'inf' preceding);
id | f_numeric | first_value | last_value
----+-----------+-------------+------------
0 | -Infinity | 0 | 0
1 | -3 | 0 | 0
2 | -1 | 0 | 0
3 | 0 | 0 | 0
4 | 1.1 | 0 | 0
5 | 1.12 | 0 | 0
6 | 2 | 0 | 0
7 | 100 | 0 | 0
8 | Infinity | 0 | 8
9 | NaN | 9 | 9
(10 rows)
select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics
window w as (order by f_numeric range between
'inf' following and 'inf' following);
id | f_numeric | first_value | last_value
----+-----------+-------------+------------
0 | -Infinity | 0 | 8
1 | -3 | 8 | 8
2 | -1 | 8 | 8
3 | 0 | 8 | 8
4 | 1.1 | 8 | 8
5 | 1.12 | 8 | 8
6 | 2 | 8 | 8
7 | 100 | 8 | 8
8 | Infinity | 8 | 8
9 | NaN | 9 | 9
(10 rows)
select id, f_numeric, first_value(id) over w, last_value(id) over w select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics from numerics
window w as (order by f_numeric range between window w as (order by f_numeric range between
......
...@@ -53,6 +53,8 @@ SELECT var_pop('nan'::float4), var_samp('nan'::float4); ...@@ -53,6 +53,8 @@ SELECT var_pop('nan'::float4), var_samp('nan'::float4);
SELECT stddev_pop('nan'::float4), stddev_samp('nan'::float4); SELECT stddev_pop('nan'::float4), stddev_samp('nan'::float4);
SELECT var_pop(1.0::numeric), var_samp(2.0::numeric); SELECT var_pop(1.0::numeric), var_samp(2.0::numeric);
SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric); SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric);
SELECT var_pop('inf'::numeric), var_samp('inf'::numeric);
SELECT stddev_pop('inf'::numeric), stddev_samp('inf'::numeric);
SELECT var_pop('nan'::numeric), var_samp('nan'::numeric); SELECT var_pop('nan'::numeric), var_samp('nan'::numeric);
SELECT stddev_pop('nan'::numeric), stddev_samp('nan'::numeric); SELECT stddev_pop('nan'::numeric), stddev_samp('nan'::numeric);
...@@ -69,14 +71,26 @@ select sum('NaN'::numeric) from generate_series(1,3); ...@@ -69,14 +71,26 @@ select sum('NaN'::numeric) from generate_series(1,3);
select avg('NaN'::numeric) from generate_series(1,3); select avg('NaN'::numeric) from generate_series(1,3);
-- verify correct results for infinite inputs -- verify correct results for infinite inputs
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('1'), ('infinity')) v(x); FROM (VALUES ('1'), ('infinity')) v(x);
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('infinity'), ('1')) v(x); FROM (VALUES ('infinity'), ('1')) v(x);
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('infinity'), ('infinity')) v(x); FROM (VALUES ('infinity'), ('infinity')) v(x);
SELECT avg(x::float8), var_pop(x::float8) SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('-infinity'), ('infinity')) v(x);
SELECT sum(x::float8), avg(x::float8), var_pop(x::float8)
FROM (VALUES ('-infinity'), ('-infinity')) v(x);
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('1'), ('infinity')) v(x);
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('infinity'), ('1')) v(x);
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('infinity'), ('infinity')) v(x);
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('-infinity'), ('infinity')) v(x); FROM (VALUES ('-infinity'), ('infinity')) v(x);
SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric)
FROM (VALUES ('-infinity'), ('-infinity')) v(x);
-- test accuracy with a large input offset -- test accuracy with a large input offset
SELECT avg(x::float8), var_pop(x::float8) SELECT avg(x::float8), var_pop(x::float8)
......
...@@ -634,6 +634,119 @@ SELECT t1.id1, t1.result, t2.expected ...@@ -634,6 +634,119 @@ SELECT t1.id1, t1.result, t2.expected
WHERE t1.id1 = t2.id WHERE t1.id1 = t2.id
AND t1.result != t2.expected; AND t1.result != t2.expected;
-- ******************************
-- * Check behavior with Inf and NaN inputs. It's easiest to handle these
-- * separately from the num_data framework used above, because some input
-- * combinations will throw errors.
-- ******************************
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
SELECT x1, x2,
x1 + x2 AS sum,
x1 - x2 AS diff,
x1 * x2 AS prod
FROM v AS v1(x1), v AS v2(x2);
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
SELECT x1, x2,
x1 / x2 AS quot,
x1 % x2 AS mod,
div(x1, x2) AS div
FROM v AS v1(x1), v AS v2(x2) WHERE x2 != 0;
SELECT 'inf'::numeric / '0';
SELECT '-inf'::numeric / '0';
SELECT 'nan'::numeric / '0';
SELECT '0'::numeric / '0';
SELECT 'inf'::numeric % '0';
SELECT '-inf'::numeric % '0';
SELECT 'nan'::numeric % '0';
SELECT '0'::numeric % '0';
SELECT div('inf'::numeric, '0');
SELECT div('-inf'::numeric, '0');
SELECT div('nan'::numeric, '0');
SELECT div('0'::numeric, '0');
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
SELECT x, -x as minusx, abs(x), floor(x), ceil(x), sign(x), numeric_inc(x) as inc
FROM v;
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
SELECT x, round(x), round(x,1) as round1, trunc(x), trunc(x,1) as trunc1
FROM v;
-- the large values fall into the numeric abbreviation code's maximal classes
WITH v(x) AS
(VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('1e340'),('-1e340'),
('inf'),('-inf'),('nan'),
('inf'),('-inf'),('nan'))
SELECT substring(x::text, 1, 32)
FROM v ORDER BY x;
WITH v(x) AS
(VALUES('0'::numeric),('1'),('4.2'),('inf'),('nan'))
SELECT x, sqrt(x)
FROM v;
SELECT sqrt('-1'::numeric);
SELECT sqrt('-inf'::numeric);
WITH v(x) AS
(VALUES('1'::numeric),('4.2'),('inf'),('nan'))
SELECT x,
log(x),
log10(x),
ln(x)
FROM v;
SELECT ln('0'::numeric);
SELECT ln('-1'::numeric);
SELECT ln('-inf'::numeric);
WITH v(x) AS
(VALUES('2'::numeric),('4.2'),('inf'),('nan'))
SELECT x1, x2,
log(x1, x2)
FROM v AS v1(x1), v AS v2(x2);
SELECT log('0'::numeric, '10');
SELECT log('10'::numeric, '0');
SELECT log('-inf'::numeric, '10');
SELECT log('10'::numeric, '-inf');
SELECT log('inf'::numeric, '0');
SELECT log('inf'::numeric, '-inf');
SELECT log('-inf'::numeric, 'inf');
WITH v(x) AS
(VALUES('0'::numeric),('1'),('2'),('4.2'),('inf'),('nan'))
SELECT x1, x2,
power(x1, x2)
FROM v AS v1(x1), v AS v2(x2) WHERE x1 != 0 OR x2 >= 0;
SELECT power('0'::numeric, '-1');
SELECT power('0'::numeric, '-inf');
SELECT power('-1'::numeric, 'inf');
SELECT power('-2'::numeric, '3');
SELECT power('-2'::numeric, '3.3');
SELECT power('-2'::numeric, '-1');
SELECT power('-2'::numeric, '-1.5');
SELECT power('-2'::numeric, 'inf');
SELECT power('-2'::numeric, '-inf');
SELECT power('inf'::numeric, '-2');
SELECT power('inf'::numeric, '-inf');
SELECT power('-inf'::numeric, '2');
SELECT power('-inf'::numeric, '3');
SELECT power('-inf'::numeric, '4.5');
SELECT power('-inf'::numeric, '-2');
SELECT power('-inf'::numeric, '-3');
SELECT power('-inf'::numeric, '0');
SELECT power('-inf'::numeric, 'inf');
SELECT power('-inf'::numeric, '-inf');
-- ****************************** -- ******************************
-- * miscellaneous checks for things that have been broken in the past... -- * miscellaneous checks for things that have been broken in the past...
-- ****************************** -- ******************************
...@@ -652,6 +765,9 @@ INSERT INTO fract_only VALUES (5, '0.99994'); ...@@ -652,6 +765,9 @@ INSERT INTO fract_only VALUES (5, '0.99994');
INSERT INTO fract_only VALUES (6, '0.99995'); -- should fail INSERT INTO fract_only VALUES (6, '0.99995'); -- should fail
INSERT INTO fract_only VALUES (7, '0.00001'); INSERT INTO fract_only VALUES (7, '0.00001');
INSERT INTO fract_only VALUES (8, '0.00017'); INSERT INTO fract_only VALUES (8, '0.00017');
INSERT INTO fract_only VALUES (9, 'NaN');
INSERT INTO fract_only VALUES (10, 'Inf'); -- should fail
INSERT INTO fract_only VALUES (11, '-Inf'); -- should fail
SELECT * FROM fract_only; SELECT * FROM fract_only;
DROP TABLE fract_only; DROP TABLE fract_only;
...@@ -659,9 +775,25 @@ DROP TABLE fract_only; ...@@ -659,9 +775,25 @@ DROP TABLE fract_only;
SELECT 'NaN'::float8::numeric; SELECT 'NaN'::float8::numeric;
SELECT 'Infinity'::float8::numeric; SELECT 'Infinity'::float8::numeric;
SELECT '-Infinity'::float8::numeric; SELECT '-Infinity'::float8::numeric;
SELECT 'NaN'::numeric::float8;
SELECT 'Infinity'::numeric::float8;
SELECT '-Infinity'::numeric::float8;
SELECT 'NaN'::float4::numeric; SELECT 'NaN'::float4::numeric;
SELECT 'Infinity'::float4::numeric; SELECT 'Infinity'::float4::numeric;
SELECT '-Infinity'::float4::numeric; SELECT '-Infinity'::float4::numeric;
SELECT 'NaN'::numeric::float4;
SELECT 'Infinity'::numeric::float4;
SELECT '-Infinity'::numeric::float4;
SELECT '42'::int2::numeric;
SELECT 'NaN'::numeric::int2;
SELECT 'Infinity'::numeric::int2;
SELECT '-Infinity'::numeric::int2;
SELECT 'NaN'::numeric::int4;
SELECT 'Infinity'::numeric::int4;
SELECT '-Infinity'::numeric::int4;
SELECT 'NaN'::numeric::int8;
SELECT 'Infinity'::numeric::int8;
SELECT '-Infinity'::numeric::int8;
-- Simple check that ceil(), floor(), and round() work correctly -- Simple check that ceil(), floor(), and round() work correctly
CREATE TABLE ceil_floor_round (a numeric); CREATE TABLE ceil_floor_round (a numeric);
...@@ -697,6 +829,9 @@ SELECT width_bucket(5.0::float8, 3.0::float8, 4.0::float8, -5); ...@@ -697,6 +829,9 @@ SELECT width_bucket(5.0::float8, 3.0::float8, 4.0::float8, -5);
SELECT width_bucket(3.5::float8, 3.0::float8, 3.0::float8, 888); SELECT width_bucket(3.5::float8, 3.0::float8, 3.0::float8, 888);
SELECT width_bucket('NaN', 3.0, 4.0, 888); SELECT width_bucket('NaN', 3.0, 4.0, 888);
SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888); SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888);
SELECT width_bucket('inf', 3.0, 4.0, 888);
SELECT width_bucket(2.0, 3.0, '-inf', 888);
SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888);
-- normal operation -- normal operation
CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8); CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8);
...@@ -782,6 +917,30 @@ SELECT '' AS to_char_21, to_char(val, '999999SG9999999999') FROM num_data; ...@@ -782,6 +917,30 @@ SELECT '' AS to_char_21, to_char(val, '999999SG9999999999') FROM num_data;
SELECT '' AS to_char_22, to_char(val, 'FM9999999999999999.999999999999999') FROM num_data; SELECT '' AS to_char_22, to_char(val, 'FM9999999999999999.999999999999999') FROM num_data;
SELECT '' AS to_char_23, to_char(val, '9.999EEEE') FROM num_data; SELECT '' AS to_char_23, to_char(val, '9.999EEEE') FROM num_data;
WITH v(val) AS
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
SELECT val,
to_char(val, '9.999EEEE') as numeric,
to_char(val::float8, '9.999EEEE') as float8,
to_char(val::float4, '9.999EEEE') as float4
FROM v;
WITH v(val) AS
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
SELECT val,
to_char(val, 'MI9999999999.99') as numeric,
to_char(val::float8, 'MI9999999999.99') as float8,
to_char(val::float4, 'MI9999999999.99') as float4
FROM v;
WITH v(val) AS
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
SELECT val,
to_char(val, 'MI99.99') as numeric,
to_char(val::float8, 'MI99.99') as float8,
to_char(val::float4, 'MI99.99') as float4
FROM v;
SELECT '' AS to_char_24, to_char('100'::numeric, 'FM999.9'); SELECT '' AS to_char_24, to_char('100'::numeric, 'FM999.9');
SELECT '' AS to_char_25, to_char('100'::numeric, 'FM999.'); SELECT '' AS to_char_25, to_char('100'::numeric, 'FM999.');
SELECT '' AS to_char_26, to_char('100'::numeric, 'FM999'); SELECT '' AS to_char_26, to_char('100'::numeric, 'FM999');
...@@ -839,6 +998,12 @@ INSERT INTO num_input_test(n1) VALUES ('555.50'); ...@@ -839,6 +998,12 @@ INSERT INTO num_input_test(n1) VALUES ('555.50');
INSERT INTO num_input_test(n1) VALUES ('-555.50'); INSERT INTO num_input_test(n1) VALUES ('-555.50');
INSERT INTO num_input_test(n1) VALUES ('NaN '); INSERT INTO num_input_test(n1) VALUES ('NaN ');
INSERT INTO num_input_test(n1) VALUES (' nan'); INSERT INTO num_input_test(n1) VALUES (' nan');
INSERT INTO num_input_test(n1) VALUES (' inf ');
INSERT INTO num_input_test(n1) VALUES (' +inf ');
INSERT INTO num_input_test(n1) VALUES (' -inf ');
INSERT INTO num_input_test(n1) VALUES (' Infinity ');
INSERT INTO num_input_test(n1) VALUES (' +inFinity ');
INSERT INTO num_input_test(n1) VALUES (' -INFINITY ');
-- bad inputs -- bad inputs
INSERT INTO num_input_test(n1) VALUES (' '); INSERT INTO num_input_test(n1) VALUES (' ');
...@@ -849,6 +1014,7 @@ INSERT INTO num_input_test(n1) VALUES ('5 . 0'); ...@@ -849,6 +1014,7 @@ INSERT INTO num_input_test(n1) VALUES ('5 . 0');
INSERT INTO num_input_test(n1) VALUES ('5. 0 '); INSERT INTO num_input_test(n1) VALUES ('5. 0 ');
INSERT INTO num_input_test(n1) VALUES (''); INSERT INTO num_input_test(n1) VALUES ('');
INSERT INTO num_input_test(n1) VALUES (' N aN '); INSERT INTO num_input_test(n1) VALUES (' N aN ');
INSERT INTO num_input_test(n1) VALUES ('+ infinity');
SELECT * FROM num_input_test; SELECT * FROM num_input_test;
...@@ -952,6 +1118,9 @@ select 1.234 ^ 5678; ...@@ -952,6 +1118,9 @@ select 1.234 ^ 5678;
select exp(0.0); select exp(0.0);
select exp(1.0); select exp(1.0);
select exp(1.0::numeric(71,70)); select exp(1.0::numeric(71,70));
select exp('nan'::numeric);
select exp('inf'::numeric);
select exp('-inf'::numeric);
-- cases that used to generate inaccurate results -- cases that used to generate inaccurate results
select exp(32.999); select exp(32.999);
...@@ -973,6 +1142,9 @@ select * from generate_series(-100::numeric, 100::numeric, 0::numeric); ...@@ -973,6 +1142,9 @@ select * from generate_series(-100::numeric, 100::numeric, 0::numeric);
select * from generate_series(-100::numeric, 100::numeric, 'nan'::numeric); select * from generate_series(-100::numeric, 100::numeric, 'nan'::numeric);
select * from generate_series('nan'::numeric, 100::numeric, 10::numeric); select * from generate_series('nan'::numeric, 100::numeric, 10::numeric);
select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric); select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric);
select * from generate_series('inf'::numeric, 'inf'::numeric, 10::numeric);
select * from generate_series(0::numeric, 'inf'::numeric, 10::numeric);
select * from generate_series(0::numeric, '42'::numeric, '-inf'::numeric);
-- Checks maximum, output is truncated -- Checks maximum, output is truncated
select (i / (10::numeric ^ 131071))::numeric(1,0) select (i / (10::numeric ^ 131071))::numeric(1,0)
from generate_series(6 * (10::numeric ^ 131071), from generate_series(6 * (10::numeric ^ 131071),
...@@ -1040,6 +1212,7 @@ select log(3.1954752e47, 9.4792021e-73); ...@@ -1040,6 +1212,7 @@ select log(3.1954752e47, 9.4792021e-73);
-- --
select scale(numeric 'NaN'); select scale(numeric 'NaN');
select scale(numeric 'inf');
select scale(NULL::numeric); select scale(NULL::numeric);
select scale(1.12); select scale(1.12);
select scale(0); select scale(0);
...@@ -1054,6 +1227,7 @@ select scale(-13.000000000000000); ...@@ -1054,6 +1227,7 @@ select scale(-13.000000000000000);
-- --
select min_scale(numeric 'NaN') is NULL; -- should be true select min_scale(numeric 'NaN') is NULL; -- should be true
select min_scale(numeric 'inf') is NULL; -- should be true
select min_scale(0); -- no digits select min_scale(0); -- no digits
select min_scale(0.00); -- no digits again select min_scale(0.00); -- no digits again
select min_scale(1.0); -- no scale select min_scale(1.0); -- no scale
...@@ -1070,6 +1244,7 @@ select min_scale(1e100); -- very big number ...@@ -1070,6 +1244,7 @@ select min_scale(1e100); -- very big number
-- --
select trim_scale(numeric 'NaN'); select trim_scale(numeric 'NaN');
select trim_scale(numeric 'inf');
select trim_scale(1.120); select trim_scale(1.120);
select trim_scale(0); select trim_scale(0);
select trim_scale(0.00); select trim_scale(0.00);
...@@ -1096,7 +1271,11 @@ FROM (VALUES (0::numeric, 0::numeric), ...@@ -1096,7 +1271,11 @@ FROM (VALUES (0::numeric, 0::numeric),
(0::numeric, 46375::numeric), (0::numeric, 46375::numeric),
(433125::numeric, 46375::numeric), (433125::numeric, 46375::numeric),
(43312.5::numeric, 4637.5::numeric), (43312.5::numeric, 4637.5::numeric),
(4331.250::numeric, 463.75000::numeric)) AS v(a, b); (4331.250::numeric, 463.75000::numeric),
('inf', '0'),
('inf', '42'),
('inf', 'inf')
) AS v(a, b);
-- --
-- Tests for LCM() -- Tests for LCM()
...@@ -1108,7 +1287,11 @@ FROM (VALUES (0::numeric, 0::numeric), ...@@ -1108,7 +1287,11 @@ FROM (VALUES (0::numeric, 0::numeric),
(13272::numeric, 13272::numeric), (13272::numeric, 13272::numeric),
(423282::numeric, 13272::numeric), (423282::numeric, 13272::numeric),
(42328.2::numeric, 1327.2::numeric), (42328.2::numeric, 1327.2::numeric),
(4232.820::numeric, 132.72000::numeric)) AS v(a, b); (4232.820::numeric, 132.72000::numeric),
('inf', '0'),
('inf', '42'),
('inf', 'inf')
) AS v(a, b);
SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow
......
...@@ -499,7 +499,7 @@ create temp table numerics( ...@@ -499,7 +499,7 @@ create temp table numerics(
); );
insert into numerics values insert into numerics values
(0, '-infinity', '-infinity', '-1000'), -- numeric type lacks infinities (0, '-infinity', '-infinity', '-infinity'),
(1, -3, -3, -3), (1, -3, -3, -3),
(2, -1, -1, -1), (2, -1, -1, -1),
(3, 0, 0, 0), (3, 0, 0, 0),
...@@ -507,7 +507,7 @@ insert into numerics values ...@@ -507,7 +507,7 @@ insert into numerics values
(5, 1.12, 1.12, 1.12), (5, 1.12, 1.12, 1.12),
(6, 2, 2, 2), (6, 2, 2, 2),
(7, 100, 100, 100), (7, 100, 100, 100),
(8, 'infinity', 'infinity', '1000'), (8, 'infinity', 'infinity', 'infinity'),
(9, 'NaN', 'NaN', 'NaN'); (9, 'NaN', 'NaN', 'NaN');
select id, f_float4, first_value(id) over w, last_value(id) over w select id, f_float4, first_value(id) over w, last_value(id) over w
...@@ -574,6 +574,18 @@ window w as (order by f_numeric range between ...@@ -574,6 +574,18 @@ window w as (order by f_numeric range between
1 preceding and 1.1::float8 following); -- currently unsupported 1 preceding and 1.1::float8 following); -- currently unsupported
select id, f_numeric, first_value(id) over w, last_value(id) over w select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics from numerics
window w as (order by f_numeric range between
'inf' preceding and 'inf' following);
select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics
window w as (order by f_numeric range between
'inf' preceding and 'inf' preceding);
select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics
window w as (order by f_numeric range between
'inf' following and 'inf' following);
select id, f_numeric, first_value(id) over w, last_value(id) over w
from numerics
window w as (order by f_numeric range between window w as (order by f_numeric range between
1.1 preceding and 'NaN' following); -- error, NaN disallowed 1.1 preceding and 'NaN' following); -- error, NaN disallowed
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment