Re: Infinities in type numeric - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Infinities in type numeric |
Date | |
Msg-id | 1199108.1595369906@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Infinities in type numeric (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Infinities in type numeric
|
List | pgsql-hackers |
I wrote: > Dean Rasheed <dean.a.rasheed@gmail.com> writes: >> I had a look at this, and I think it's mostly in good shape. It looks >> like everything from the first message in this thread has been >> resolved, except I don't know about the jsonpath stuff, because I >> haven't been following that. > Thanks for the careful review! Here's a v4 that syncs numeric in_range() with the new behavior of float in_range(), and addresses your other comments too. regards, tom lane diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c index ed361efbe2..b81ba54b80 100644 --- a/contrib/jsonb_plperl/jsonb_plperl.c +++ b/contrib/jsonb_plperl/jsonb_plperl.c @@ -227,10 +227,8 @@ SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem) /* * jsonb doesn't allow infinity or NaN (per JSON * specification), but the numeric type that is used for the - * storage accepts NaN, so we have to prevent it here - * explicitly. We don't really have to check for isinf() - * here, as numeric doesn't allow it and it would be caught - * later, but it makes for a nicer error message. + * storage accepts those, so we have to reject them here + * explicitly. */ if (isinf(nval)) ereport(ERROR, diff --git a/contrib/jsonb_plpython/jsonb_plpython.c b/contrib/jsonb_plpython/jsonb_plpython.c index e09308daf0..836c178770 100644 --- a/contrib/jsonb_plpython/jsonb_plpython.c +++ b/contrib/jsonb_plpython/jsonb_plpython.c @@ -387,14 +387,17 @@ PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum) pfree(str); /* - * jsonb doesn't allow NaN (per JSON specification), so we have to prevent - * it here explicitly. (Infinity is also not allowed in jsonb, but - * numeric_in above already catches that.) + * jsonb doesn't allow NaN or infinity (per JSON specification), so we + * have to reject those here explicitly. */ if (numeric_is_nan(num)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), 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->val.numeric = num; diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 7027758d28..50e370cae4 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -554,9 +554,9 @@ NUMERIC(<replaceable>precision</replaceable>) <programlisting> NUMERIC </programlisting> - without any precision or scale creates a column in which numeric - values of any precision and scale can be stored, up to the - implementation limit on precision. A column of this kind will + without any precision or scale creates an <quote>unconstrained + numeric</quote> column in which numeric values of any length can be + stored, up to the implementation limits. A column of this kind will not coerce input values to any particular scale, whereas <type>numeric</type> columns with a declared scale will coerce input values to that scale. (The <acronym>SQL</acronym> standard @@ -568,10 +568,10 @@ NUMERIC <note> <para> - The maximum allowed precision when explicitly specified in the - type declaration is 1000; <type>NUMERIC</type> without a specified - precision is subject to the limits described in <xref - linkend="datatype-numeric-table"/>. + The maximum precision that can be explicitly specified in + a <type>NUMERIC</type> type declaration is 1000. An + unconstrained <type>NUMERIC</type> column is subject to the limits + described in <xref linkend="datatype-numeric-table"/>. </para> </note> @@ -593,6 +593,11 @@ NUMERIC plus three to eight bytes overhead. </para> + <indexterm> + <primary>infinity</primary> + <secondary>numeric (data type)</secondary> + </indexterm> + <indexterm> <primary>NaN</primary> <see>not a number</see> @@ -604,13 +609,44 @@ NUMERIC </indexterm> <para> - In addition to ordinary numeric values, the <type>numeric</type> - type allows the special value <literal>NaN</literal>, meaning - <quote>not-a-number</quote>. Any operation on <literal>NaN</literal> - yields another <literal>NaN</literal>. When writing this value - as a constant in an SQL command, you must put quotes around it, - for example <literal>UPDATE table SET x = 'NaN'</literal>. On input, - the string <literal>NaN</literal> is recognized in a case-insensitive manner. + In addition to ordinary numeric values, the <type>numeric</type> type + has several special values: +<literallayout> +<literal>Infinity</literal> +<literal>-Infinity</literal> +<literal>NaN</literal> +</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> <note> @@ -781,9 +817,14 @@ FROM generate_series(-3.5, 3.5, 1) as x; </para> </note> + <indexterm> + <primary>infinity</primary> + <secondary>floating point</secondary> + </indexterm> + <indexterm> <primary>not a number</primary> - <secondary>double precision</secondary> + <secondary>floating point</secondary> </indexterm> <para> @@ -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, 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> <note> <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>). In order to allow floating-point values to be sorted and used in tree-based indexes, <productname>PostgreSQL</productname> treats diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 16768b28c3..6626438136 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -6129,9 +6129,12 @@ numeric_to_char(PG_FUNCTION_ARGS) /* * 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 - * 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, @@ -6346,7 +6349,7 @@ int8_to_char(PG_FUNCTION_ARGS) /* * 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 - * 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 != '-') { diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 1773fa292e..ed825a1fdd 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -109,14 +109,13 @@ typedef int16 NumericDigit; * 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 * numeric follows the NumericShort format; if they are NUMERIC_POS or - * NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_NAN, - * it is a NaN. We currently always store a NaN using just two bytes (i.e. - * only n_header), but previous releases used only the NumericLong format, - * so we might find 4-byte NaNs on disk if a database has been migrated using - * pg_upgrade. In either case, when the high bits indicate a NaN, the - * remaining bits are never examined. Currently, we always initialize these - * to zero, but it might be possible to use them for some other purpose in - * the future. + * NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_SPECIAL, + * the value is a NaN or Infinity. We currently always store SPECIAL values + * using just two bytes (i.e. only n_header), but previous releases used only + * the NumericLong format, so we might find 4-byte NaNs (though not infinities) + * on disk if a database has been migrated using pg_upgrade. In either case, + * the low-order bits of a special value's header are reserved and currently + * should always be set to zero. * * 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 @@ -168,25 +167,47 @@ struct NumericData #define NUMERIC_POS 0x0000 #define NUMERIC_NEG 0x4000 #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_IS_NAN(n) (NUMERIC_FLAGBITS(n) == NUMERIC_NAN) #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_SHORT (VARHDRSZ + sizeof(uint16)) /* - * If the flag bits are NUMERIC_SHORT or NUMERIC_NAN, we want the short header; - * otherwise, we want the long one. Instead of testing against each value, we - * can just look at the high bit, for a slight efficiency gain. + * If the flag bits are NUMERIC_SHORT or NUMERIC_SPECIAL, we want the short + * header; otherwise, we want the long one. Instead of testing against each + * 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_SIZE(n) \ (VARHDRSZ + sizeof(uint16) + \ (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. */ @@ -202,7 +223,13 @@ struct NumericData #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 @@ -210,7 +237,9 @@ struct NumericData #define NUMERIC_SIGN(n) \ (NUMERIC_IS_SHORT(n) ? \ (((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)) ? \ ((n)->choice.n_short.n_header & NUMERIC_SHORT_DSCALE_MASK) \ >> NUMERIC_SHORT_DSCALE_SHIFT \ @@ -227,7 +256,9 @@ struct NumericData * complex. * * 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 * by NBASE ** weight. Another way to say it is that there are weight+1 @@ -274,7 +305,7 @@ typedef struct NumericVar { int ndigits; /* # of digits in digits[] - can be 0! */ 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 */ NumericDigit *buf; /* start of palloc'd space for digits[] */ NumericDigit *digits; /* base-NBASE digits */ @@ -354,16 +385,26 @@ typedef struct NumericSumAccum * representations for numeric values in order to avoid depending 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 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) #if SIZEOF_DATUM == 8 #define NumericAbbrevGetDatum(X) ((Datum) (X)) #define DatumGetNumericAbbrev(X) ((int64) (X)) #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 #define NumericAbbrevGetDatum(X) ((Datum) (X)) #define DatumGetNumericAbbrev(X) ((int32) (X)) #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 @@ -379,6 +420,9 @@ static const NumericDigit const_one_data[1] = {1}; static const NumericVar const_one = {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 NumericVar const_two = {1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data}; @@ -416,6 +460,12 @@ static const NumericVar const_one_point_one = static const NumericVar const_nan = {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 static const int round_powers[4] = {0, 1000, 100, 10}; #endif @@ -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_sci(const NumericVar *var, int rscale); +static Numeric duplicate_numeric(Numeric num); static Numeric make_result(const NumericVar *var); static Numeric make_result_opt_error(const NumericVar *var, bool *error); 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_int64(const NumericVar *var, int64 *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 void int128_to_numericvar(int128 val, NumericVar *var); #endif -static double numeric_to_double_no_overflow(Numeric num); static double numericvar_to_double_no_overflow(const NumericVar *var); static Datum numeric_abbrev_convert(Datum original_datum, SortSupport ssup); @@ -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) { res = make_result(&const_nan); - - /* Should be nothing left but spaces */ cp += 3; - 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++; - } + } + else if (pg_strncasecmp(cp, "Infinity", 8) == 0) + { + res = make_result(&const_pinf); + cp += 8; + } + else if (pg_strncasecmp(cp, "+Infinity", 9) == 0) + { + 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 { @@ -620,7 +691,7 @@ numeric_in(PG_FUNCTION_ARGS) * We duplicate a few lines of code here because we would like to * throw any trailing-junk syntax error before any semantic error * 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) { @@ -636,8 +707,24 @@ numeric_in(PG_FUNCTION_ARGS) res = make_result(&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); } @@ -655,10 +742,17 @@ numeric_out(PG_FUNCTION_ARGS) char *str; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - PG_RETURN_CSTRING(pstrdup("NaN")); + if (NUMERIC_IS_SPECIAL(num)) + { + 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. @@ -681,6 +775,41 @@ numeric_is_nan(Numeric 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() - * @@ -732,10 +861,17 @@ numeric_out_sci(Numeric num, int scale) char *str; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - return pstrdup("NaN"); + if (NUMERIC_IS_SPECIAL(num)) + { + 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); @@ -760,10 +896,17 @@ numeric_normalize(Numeric num) int last; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - return pstrdup("NaN"); + if (NUMERIC_IS_SPECIAL(num)) + { + 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); @@ -823,7 +966,9 @@ numeric_recv(PG_FUNCTION_ARGS) value.sign = (uint16) pq_getmsgint(buf, sizeof(uint16)); if (!(value.sign == NUMERIC_POS || value.sign == NUMERIC_NEG || - value.sign == NUMERIC_NAN)) + value.sign == NUMERIC_NAN || + value.sign == NUMERIC_PINF || + value.sign == NUMERIC_NINF)) ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("invalid sign in external \"numeric\" value"))); @@ -849,13 +994,29 @@ numeric_recv(PG_FUNCTION_ARGS) * 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 * 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); PG_RETURN_NUMERIC(res); @@ -961,21 +1122,21 @@ numeric (PG_FUNCTION_ARGS) 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)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + { + 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 * input value */ if (typmod < (int32) (VARHDRSZ)) - { - new = (Numeric) palloc(VARSIZE(num)); - memcpy(new, num, VARSIZE(num)); - PG_RETURN_NUMERIC(new); - } + PG_RETURN_NUMERIC(duplicate_numeric(num)); /* * Get the precision and scale out of the typmod value @@ -997,8 +1158,7 @@ numeric (PG_FUNCTION_ARGS) && (NUMERIC_CAN_BE_SHORT(scale, NUMERIC_WEIGHT(num)) || !NUMERIC_IS_SHORT(num))) { - new = (Numeric) palloc(VARSIZE(num)); - memcpy(new, num, VARSIZE(num)); + new = duplicate_numeric(num); if (NUMERIC_IS_SHORT(num)) new->choice.n_short.n_header = (num->choice.n_short.n_header & ~NUMERIC_SHORT_DSCALE_MASK) @@ -1099,21 +1259,20 @@ numeric_abs(PG_FUNCTION_ARGS) Numeric num = PG_GETARG_NUMERIC(0); 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 */ - res = (Numeric) palloc(VARSIZE(num)); - memcpy(res, num, VARSIZE(num)); + res = duplicate_numeric(num); if (NUMERIC_IS_SHORT(num)) res->choice.n_short.n_header = 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 res->choice.n_long.n_sign_dscale = NUMERIC_POS | NUMERIC_DSCALE(num); @@ -1127,24 +1286,25 @@ numeric_uminus(PG_FUNCTION_ARGS) Numeric num = PG_GETARG_NUMERIC(0); 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 */ - res = (Numeric) palloc(VARSIZE(num)); - memcpy(res, num, VARSIZE(num)); + res = duplicate_numeric(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 - * we can identify a ZERO by the fact that there are no digits at all. Do - * nothing to a zero. + * once we've eliminated specials, we can identify a zero by the fact that + * 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 */ if (NUMERIC_IS_SHORT(num)) @@ -1166,12 +1326,42 @@ Datum numeric_uplus(PG_FUNCTION_ARGS) { Numeric num = PG_GETARG_NUMERIC(0); - Numeric res; - res = (Numeric) palloc(VARSIZE(num)); - memcpy(res, num, VARSIZE(num)); + PG_RETURN_NUMERIC(duplicate_numeric(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 numeric_sign(PG_FUNCTION_ARGS) { Numeric num = PG_GETARG_NUMERIC(0); - Numeric res; - NumericVar result; /* - * Handle NaN + * Handle NaN (infinities can be handled normally) */ if (NUMERIC_IS_NAN(num)) PG_RETURN_NUMERIC(make_result(&const_nan)); - init_var(&result); - - /* - * 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 + switch (numeric_sign_internal(num)) { - /* - * And if there are some, we return a copy of ONE with the sign of our - * argument - */ - set_var_from_var(&const_one, &result); - result.sign = NUMERIC_SIGN(num); + case 0: + PG_RETURN_NUMERIC(make_result(&const_zero)); + case 1: + PG_RETURN_NUMERIC(make_result(&const_one)); + case -1: + PG_RETURN_NUMERIC(make_result(&const_minus_one)); } - res = make_result(&result); - free_var(&result); - - PG_RETURN_NUMERIC(res); + Assert(false); + return (Datum) 0; } @@ -1234,10 +1412,10 @@ numeric_round(PG_FUNCTION_ARGS) NumericVar arg; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); /* * Limit the scale value to avoid possible overflow in calculations @@ -1283,10 +1461,10 @@ numeric_trunc(PG_FUNCTION_ARGS) NumericVar arg; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); /* * Limit the scale value to avoid possible overflow in calculations @@ -1328,8 +1506,11 @@ numeric_ceil(PG_FUNCTION_ARGS) Numeric res; 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); ceil_var(&result, &result); @@ -1353,8 +1534,11 @@ numeric_floor(PG_FUNCTION_ARGS) Numeric res; 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); floor_var(&result, &result); @@ -1390,26 +1574,46 @@ generate_series_step_numeric(PG_FUNCTION_ARGS) Numeric stop_num = PG_GETARG_NUMERIC(1); NumericVar steploc = const_one; - /* handle NaN in start and stop values */ - if (NUMERIC_IS_NAN(start_num)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("start value cannot be NaN"))); - - if (NUMERIC_IS_NAN(stop_num)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("stop value cannot be NaN"))); + /* Reject NaN and infinities in start and stop values */ + if (NUMERIC_IS_SPECIAL(start_num)) + { + if (NUMERIC_IS_NAN(start_num)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("start value cannot be NaN"))); + else + ereport(ERROR, + (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 */ if (PG_NARGS() == 3) { Numeric step_num = PG_GETARG_NUMERIC(2); - if (NUMERIC_IS_NAN(step_num)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("step size cannot be NaN"))); + if (NUMERIC_IS_SPECIAL(step_num)) + { + if (NUMERIC_IS_NAN(step_num)) + 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); @@ -1510,12 +1714,21 @@ width_bucket_numeric(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION), errmsg("count must be greater than zero"))); - if (NUMERIC_IS_NAN(operand) || - 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"))); + if (NUMERIC_IS_SPECIAL(operand) || + NUMERIC_IS_SPECIAL(bound1) || + NUMERIC_IS_SPECIAL(bound2)) + { + if (NUMERIC_IS_NAN(operand) || + 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(&count_var); @@ -1719,9 +1932,14 @@ numeric_abbrev_convert(Datum original_datum, SortSupport ssup) else 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 { @@ -1847,7 +2065,7 @@ numeric_cmp_abbrev(Datum x, Datum y, SortSupport ssup) { /* * 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)) return 1; @@ -2150,20 +2368,42 @@ cmp_numerics(Numeric num1, Numeric num2) int result; /* - * We consider all NANs to be equal and larger than any non-NAN. This is - * somewhat arbitrary; the important thing is to have a consistent sort - * order. + * We consider all NANs to be equal and larger than any non-NAN (including + * Infinity). This is somewhat arbitrary; the important thing is to have + * a consistent sort order. */ - if (NUMERIC_IS_NAN(num1)) + if (NUMERIC_IS_SPECIAL(num1)) { - if (NUMERIC_IS_NAN(num2)) - result = 0; /* NAN = NAN */ - else - result = 1; /* NAN > non-NAN */ + if (NUMERIC_IS_NAN(num1)) + { + if (NUMERIC_IS_NAN(num2)) + 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 { @@ -2190,10 +2430,12 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS) bool result; /* - * Reject negative or NaN offset. Negative is per spec, and NaN is - * because appropriate semantics for that seem non-obvious. + * Reject negative (including -Inf) or NaN offset. Negative is per spec, + * 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, (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE), errmsg("invalid preceding or following size in window function"))); @@ -2214,6 +2456,67 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS) { 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 { /* @@ -2264,8 +2567,8 @@ hash_numeric(PG_FUNCTION_ARGS) int hash_len; NumericDigit *digits; - /* If it's NaN, don't try to hash the rest of the fields */ - 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_UINT32(0); weight = NUMERIC_WEIGHT(key); @@ -2345,7 +2648,8 @@ hash_numeric_extended(PG_FUNCTION_ARGS) int hash_len; 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); weight = NUMERIC_WEIGHT(key); @@ -2429,10 +2733,32 @@ numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error) Numeric res; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - return make_result(&const_nan); + 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_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. @@ -2485,10 +2811,32 @@ numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error) Numeric res; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - return make_result(&const_nan); + 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_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. @@ -2541,10 +2889,64 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error) Numeric res; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - return make_result(&const_nan); + 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_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. @@ -2605,10 +3007,67 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error) *have_error = false; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - return make_result(&const_nan); + 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_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 @@ -2661,10 +3120,57 @@ numeric_div_trunc(PG_FUNCTION_ARGS) Numeric res; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + 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 @@ -2723,8 +3229,34 @@ numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error) if (have_error) *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(num2, &arg2); @@ -2763,10 +3295,10 @@ numeric_inc(PG_FUNCTION_ARGS) Numeric res; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); /* * Compute the result and return it @@ -2850,9 +3382,10 @@ numeric_gcd(PG_FUNCTION_ARGS) 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)); /* @@ -2892,9 +3425,10 @@ numeric_lcm(PG_FUNCTION_ARGS) 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)); /* @@ -3003,10 +3537,18 @@ numeric_sqrt(PG_FUNCTION_ARGS) int rscale; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + { + /* 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 @@ -3054,10 +3596,16 @@ numeric_exp(PG_FUNCTION_ARGS) double val; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + { + /* 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 @@ -3115,10 +3663,17 @@ numeric_ln(PG_FUNCTION_ARGS) int rscale; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + { + 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(&result); @@ -3157,10 +3712,39 @@ numeric_log(PG_FUNCTION_ARGS) NumericVar result; /* - * Handle NaN + * Handle NaN and infinities */ - if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + 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 @@ -3186,7 +3770,7 @@ numeric_log(PG_FUNCTION_ARGS) /* * numeric_power() - * - * Raise b to the power of x + * Raise x to the power of y */ Datum numeric_power(PG_FUNCTION_ARGS) @@ -3196,60 +3780,170 @@ numeric_power(PG_FUNCTION_ARGS) Numeric res; NumericVar arg1; NumericVar arg2; - NumericVar arg2_trunc; NumericVar result; + int sign1, + sign2; /* - * Handle NaN cases. 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). + * Handle NaN and infinities */ - 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); - if (cmp_var(&arg2, &const_zero) == 0) + 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)); - } - if (NUMERIC_IS_NAN(num2)) - { - init_var_from_num(num1, &arg1); - if (cmp_var(&arg1, &const_one) == 0) + + /* + * For any value of x, if y is [-]0, 1.0 shall be returned. + */ + if (sign2 == 0) PG_RETURN_NUMERIC(make_result(&const_one)); - PG_RETURN_NUMERIC(make_result(&const_nan)); - } - /* - * Initialize things - */ - init_var(&arg2_trunc); - init_var(&result); - init_var_from_num(num1, &arg1); - init_var_from_num(num2, &arg2); + /* + * 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 + * be returned. (Since we don't deal in minus zero, we need not + * distinguish these two cases.) + */ + 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 * certain error conditions. Specifically, we don't return a * divide-by-zero error code for 0 ^ -1. */ - if (cmp_var(&arg1, &const_zero) == 0 && - cmp_var(&arg2, &const_zero) < 0) + 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 (cmp_var(&arg1, &const_zero) < 0 && - cmp_var(&arg2, &arg2_trunc) != 0) + 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"))); + /* + * 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 * scale selection itself. @@ -3259,7 +3953,6 @@ numeric_power(PG_FUNCTION_ARGS) res = make_result(&result); free_var(&result); - free_var(&arg2_trunc); PG_RETURN_NUMERIC(res); } @@ -3274,7 +3967,7 @@ numeric_scale(PG_FUNCTION_ARGS) { Numeric num = PG_GETARG_NUMERIC(0); - if (NUMERIC_IS_NAN(num)) + if (NUMERIC_IS_SPECIAL(num)) PG_RETURN_NULL(); PG_RETURN_INT32(NUMERIC_DSCALE(num)); @@ -3341,7 +4034,7 @@ numeric_min_scale(PG_FUNCTION_ARGS) NumericVar arg; int min_scale; - if (NUMERIC_IS_NAN(num)) + if (NUMERIC_IS_SPECIAL(num)) PG_RETURN_NULL(); init_var_from_num(num, &arg); @@ -3361,8 +4054,8 @@ numeric_trim_scale(PG_FUNCTION_ARGS) Numeric res; NumericVar result; - if (NUMERIC_IS_NAN(num)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + if (NUMERIC_IS_SPECIAL(num)) + PG_RETURN_NUMERIC(duplicate_numeric(num)); init_var_from_num(num, &result); result.dscale = get_min_scale(&result); @@ -3408,8 +4101,7 @@ numeric_int4_opt_error(Numeric num, bool *have_error) if (have_error) *have_error = false; - /* XXX would it be better to return NULL? */ - if (NUMERIC_IS_NAN(num)) + if (NUMERIC_IS_SPECIAL(num)) { if (have_error) { @@ -3418,9 +4110,14 @@ numeric_int4_opt_error(Numeric num, bool *have_error) } else { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert NaN to integer"))); + if (NUMERIC_IS_NAN(num)) + ereport(ERROR, + (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) NumericVar x; int64 result; - /* XXX would it be better to return NULL? */ - if (NUMERIC_IS_NAN(num)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert NaN to bigint"))); + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + ereport(ERROR, + (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 */ init_var_from_num(num, &x); @@ -3544,11 +4247,17 @@ numeric_int2(PG_FUNCTION_ARGS) int64 val; int16 result; - /* XXX would it be better to return NULL? */ - if (NUMERIC_IS_NAN(num)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert NaN to smallint"))); + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + ereport(ERROR, + (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 */ init_var_from_num(num, &x); @@ -3583,9 +4292,12 @@ float8_numeric(PG_FUNCTION_ARGS) PG_RETURN_NUMERIC(make_result(&const_nan)); if (isinf(val)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert infinity to numeric"))); + { + if (val < 0) + PG_RETURN_NUMERIC(make_result(&const_ninf)); + else + PG_RETURN_NUMERIC(make_result(&const_pinf)); + } snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val); @@ -3609,8 +4321,15 @@ numeric_float8(PG_FUNCTION_ARGS) char *tmp; Datum result; - if (NUMERIC_IS_NAN(num)) - PG_RETURN_FLOAT8(get_float8_nan()); + if (NUMERIC_IS_SPECIAL(num)) + { + 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, NumericGetDatum(num))); @@ -3634,10 +4353,22 @@ numeric_float8_no_overflow(PG_FUNCTION_ARGS) Numeric num = PG_GETARG_NUMERIC(0); double val; - if (NUMERIC_IS_NAN(num)) - PG_RETURN_FLOAT8(get_float8_nan()); + if (NUMERIC_IS_SPECIAL(num)) + { + 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); } @@ -3654,9 +4385,12 @@ float4_numeric(PG_FUNCTION_ARGS) PG_RETURN_NUMERIC(make_result(&const_nan)); if (isinf(val)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert infinity to numeric"))); + { + if (val < 0) + PG_RETURN_NUMERIC(make_result(&const_ninf)); + else + PG_RETURN_NUMERIC(make_result(&const_pinf)); + } snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val); @@ -3680,8 +4414,15 @@ numeric_float4(PG_FUNCTION_ARGS) char *tmp; Datum result; - if (NUMERIC_IS_NAN(num)) - PG_RETURN_FLOAT4(get_float4_nan()); + if (NUMERIC_IS_SPECIAL(num)) + { + 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, NumericGetDatum(num))); @@ -3701,10 +4442,17 @@ numeric_pg_lsn(PG_FUNCTION_ARGS) NumericVar x; XLogRecPtr result; - if (NUMERIC_IS_NAN(num)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert NaN to pg_lsn"))); + if (NUMERIC_IS_SPECIAL(num)) + { + if (NUMERIC_IS_NAN(num)) + 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 */ init_var_from_num(num, &x); @@ -3741,9 +4489,15 @@ typedef struct NumericAggState NumericSumAccum sumX2; /* sum of squares of processed numbers */ int maxScale; /* maximum scale seen so far */ 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; +#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 * sum, count and optionally sum of squares of the input. @@ -3795,10 +4549,15 @@ do_numeric_accum(NumericAggState *state, Numeric newval) NumericVar X2; MemoryContext old_context; - /* Count NaN inputs separately from all else */ - if (NUMERIC_IS_NAN(newval)) + /* Count NaN/infinity inputs separately from all else */ + 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; } @@ -3860,10 +4619,15 @@ do_numeric_discard(NumericAggState *state, Numeric newval) NumericVar X2; MemoryContext old_context; - /* Count NaN inputs separately from all else */ - if (NUMERIC_IS_NAN(newval)) + /* Count NaN/infinity inputs separately from all else */ + 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; } @@ -3986,6 +4750,8 @@ numeric_combine(PG_FUNCTION_ARGS) state1 = makeNumericAggStateCurrentContext(true); state1->N = state2->N; state1->NaNcount = state2->NaNcount; + state1->pInfcount = state2->pInfcount; + state1->nInfcount = state2->nInfcount; state1->maxScale = state2->maxScale; state1->maxScaleCount = state2->maxScaleCount; @@ -3999,6 +4765,8 @@ numeric_combine(PG_FUNCTION_ARGS) state1->N += state2->N; state1->NaNcount += state2->NaNcount; + state1->pInfcount += state2->pInfcount; + state1->nInfcount += state2->nInfcount; if (state2->N > 0) { @@ -4074,6 +4842,8 @@ numeric_avg_combine(PG_FUNCTION_ARGS) state1 = makeNumericAggStateCurrentContext(false); state1->N = state2->N; state1->NaNcount = state2->NaNcount; + state1->pInfcount = state2->pInfcount; + state1->nInfcount = state2->nInfcount; state1->maxScale = state2->maxScale; state1->maxScaleCount = state2->maxScaleCount; @@ -4086,6 +4856,8 @@ numeric_avg_combine(PG_FUNCTION_ARGS) state1->N += state2->N; state1->NaNcount += state2->NaNcount; + state1->pInfcount += state2->pInfcount; + state1->nInfcount += state2->nInfcount; if (state2->N > 0) { @@ -4164,6 +4936,12 @@ numeric_avg_serialize(PG_FUNCTION_ARGS) /* NaNcount */ pq_sendint64(&buf, state->NaNcount); + /* pInfcount */ + pq_sendint64(&buf, state->pInfcount); + + /* nInfcount */ + pq_sendint64(&buf, state->nInfcount); + result = pq_endtypsend(&buf); PG_RETURN_BYTEA_P(result); @@ -4218,6 +4996,12 @@ numeric_avg_deserialize(PG_FUNCTION_ARGS) /* NaNcount */ result->NaNcount = pq_getmsgint64(&buf); + /* pInfcount */ + result->pInfcount = pq_getmsgint64(&buf); + + /* nInfcount */ + result->nInfcount = pq_getmsgint64(&buf); + pq_getmsgend(&buf); pfree(buf.data); @@ -4286,6 +5070,12 @@ numeric_serialize(PG_FUNCTION_ARGS) /* NaNcount */ pq_sendint64(&buf, state->NaNcount); + /* pInfcount */ + pq_sendint64(&buf, state->pInfcount); + + /* nInfcount */ + pq_sendint64(&buf, state->nInfcount); + result = pq_endtypsend(&buf); PG_RETURN_BYTEA_P(result); @@ -4349,6 +5139,12 @@ numeric_deserialize(PG_FUNCTION_ARGS) /* NaNcount */ result->NaNcount = pq_getmsgint64(&buf); + /* pInfcount */ + result->pInfcount = pq_getmsgint64(&buf); + + /* nInfcount */ + result->nInfcount = pq_getmsgint64(&buf); + pq_getmsgend(&buf); pfree(buf.data); @@ -5141,12 +5937,20 @@ numeric_avg(PG_FUNCTION_ARGS) state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); /* 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(); if (state->NaNcount > 0) /* there was at least one NaN input */ 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)); init_var(&sumX_var); @@ -5167,12 +5971,20 @@ numeric_sum(PG_FUNCTION_ARGS) state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0); /* 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(); if (state->NaNcount > 0) /* there was at least one NaN input */ 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); accum_sum_final(&state->sumX, &sumX_var); result = make_result(&sumX_var); @@ -5208,9 +6020,9 @@ numeric_stddev_internal(NumericAggState *state, /* * Sample stddev and variance are undefined when N <= 1; population stddev * 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; return NULL; @@ -5225,9 +6037,10 @@ numeric_stddev_internal(NumericAggState *state, *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); /* OK, normal calculation applies */ @@ -5870,6 +6683,12 @@ dump_numeric(const char *str, Numeric num) case NUMERIC_NAN: printf("NaN"); break; + case NUMERIC_PINF: + printf("Infinity"); + break; + case NUMERIC_NINF: + printf("-Infinity"); + break; default: printf("SIGN=0x%x", NUMERIC_SIGN(num)); break; @@ -5901,6 +6720,12 @@ dump_var(const char *str, NumericVar *var) case NUMERIC_NAN: printf("NaN"); break; + case NUMERIC_PINF: + printf("Infinity"); + break; + case NUMERIC_NINF: + printf("-Infinity"); + break; default: printf("SIGN=0x%x", var->sign); break; @@ -5918,8 +6743,9 @@ dump_var(const char *str, NumericVar *var) * * Local functions follow * - * In general, these do not support NaNs --- callers must eliminate - * the possibility of NaN first. (make_result() is an exception.) + * In general, these do not support "special" (NaN or infinity) inputs; + * 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) * * Parse a string and put the number into a variable * - * This function does not handle leading or trailing spaces, and it doesn't - * accept "NaN" either. It returns the end+1 position so that caller can - * check for trailing spaces/garbage if deemed necessary. + * This function does not handle leading or trailing spaces. It returns + * the end+1 position parsed, so that caller can check for trailing + * spaces/garbage if deemed necessary. * * 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.) @@ -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() - * * 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 - * true, NULL returned. This is helpful when caller need to handle errors - * by itself. + * a variable. This will handle NaN and Infinity cases. + * + * 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 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) *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); SET_VARSIZE(result, NUMERIC_HDRSZ_SHORT); - result->choice.n_header = NUMERIC_NAN; + result->choice.n_header = sign; /* the header word is all we need */ dump_numeric("make_result()", result); @@ -6572,8 +7424,8 @@ make_result(const NumericVar *var) /* * apply_typmod() - * - * Do bounds checking and rounding according to the attributes - * typmod field. + * Do bounds checking and rounding according to the specified typmod. + * Note that this is only applied to normal finite values. */ static void 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. * @@ -6961,36 +7852,9 @@ int128_to_numericvar(int128 val, NumericVar *var) #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 -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) { char *tmp; diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h index 0604cb65ed..0b7d4ba3c4 100644 --- a/src/include/utils/numeric.h +++ b/src/include/utils/numeric.h @@ -57,6 +57,7 @@ typedef struct NumericData *Numeric; * Utility functions in numeric.c */ extern bool numeric_is_nan(Numeric num); +extern bool numeric_is_inf(Numeric num); int32 numeric_maximum_size(int32 typmod); extern char *numeric_out_sci(Numeric num, int scale); extern char *numeric_normalize(Numeric num); diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 3bd184ae29..477fd1205c 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -211,6 +211,18 @@ SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric); 0 | (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); var_pop | var_samp ---------+---------- @@ -285,32 +297,74 @@ select avg('NaN'::numeric) from generate_series(1,3); (1 row) -- 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); - avg | var_pop -----------+--------- - Infinity | NaN + sum | avg | var_pop +----------+----------+--------- + Infinity | Infinity | NaN (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); - avg | var_pop -----------+--------- - Infinity | NaN + sum | avg | var_pop +----------+----------+--------- + Infinity | Infinity | NaN (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); - avg | var_pop -----------+--------- - Infinity | NaN + sum | avg | var_pop +----------+----------+--------- + Infinity | Infinity | NaN (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); - avg | var_pop ------+--------- - NaN | NaN + sum | avg | var_pop +-----+-----+--------- + 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) -- test accuracy with a large input offset diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index 81a0c5d40f..8546ce901f 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -660,6 +660,432 @@ SELECT t1.id1, t1.result, t2.expected -----+--------+---------- (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... -- ****************************** @@ -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. INSERT INTO fract_only VALUES (7, '0.00001'); 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; id | val ----+--------- @@ -705,7 +1138,8 @@ SELECT * FROM fract_only; 5 | 0.9999 7 | 0.0000 8 | 0.0002 -(6 rows) + 9 | NaN +(7 rows) DROP TABLE fract_only; -- Check inf/nan conversion behavior @@ -716,9 +1150,35 @@ SELECT 'NaN'::float8::numeric; (1 row) SELECT 'Infinity'::float8::numeric; -ERROR: cannot convert infinity to numeric + numeric +---------- + Infinity +(1 row) + 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; numeric --------- @@ -726,9 +1186,59 @@ SELECT 'NaN'::float4::numeric; (1 row) SELECT 'Infinity'::float4::numeric; -ERROR: cannot convert infinity to numeric + numeric +---------- + Infinity +(1 row) + 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 CREATE TABLE ceil_floor_round (a numeric); INSERT INTO ceil_floor_round VALUES ('-5.5'); @@ -794,6 +1304,12 @@ SELECT width_bucket('NaN', 3.0, 4.0, 888); ERROR: operand, lower bound, and upper bound cannot be NaN SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888); 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 CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8); 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; | -2.493e+07 (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'); to_char_24 | to_char ------------+--------- @@ -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 ('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 INSERT INTO num_input_test(n1) VALUES (' '); ERROR: invalid input syntax for type numeric: " " @@ -1459,17 +2035,27 @@ INSERT INTO num_input_test(n1) VALUES (' N aN '); ERROR: invalid input syntax for type numeric: " 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; - n1 ---------- - 123 - 3245874 - -93853 - 555.50 - -555.50 - NaN - NaN -(7 rows) + n1 +----------- + 123 + 3245874 + -93853 + 555.50 + -555.50 + NaN + NaN + Infinity + Infinity + -Infinity + Infinity + Infinity + -Infinity +(13 rows) -- -- Test some corner cases for multiplication @@ -1805,6 +2391,24 @@ select exp(1.0::numeric(71,70)); 2.7182818284590452353602874713526624977572470936999595749669676277240766 (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 select exp(32.999); exp @@ -1876,6 +2480,12 @@ select * from generate_series('nan'::numeric, 100::numeric, 10::numeric); ERROR: start value cannot be NaN select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric); 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 select (i / (10::numeric ^ 131071))::numeric(1,0) from generate_series(6 * (10::numeric ^ 131071), @@ -2081,6 +2691,12 @@ select scale(numeric 'NaN'); (1 row) +select scale(numeric 'inf'); + scale +------- + +(1 row) + select scale(NULL::numeric); scale ------- @@ -2138,6 +2754,12 @@ select min_scale(numeric 'NaN') is NULL; -- should be true t (1 row) +select min_scale(numeric 'inf') is NULL; -- should be true + ?column? +---------- + t +(1 row) + select min_scale(0); -- no digits min_scale ----------- @@ -2207,6 +2829,12 @@ select trim_scale(numeric 'NaN'); NaN (1 row) +select trim_scale(numeric 'inf'); + trim_scale +------------ + Infinity +(1 row) + select trim_scale(1.120); trim_scale ------------ @@ -2280,7 +2908,11 @@ FROM (VALUES (0::numeric, 0::numeric), (0::numeric, 46375::numeric), (433125::numeric, 46375::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 ----------+-----------+---------+---------+---------+--------- 0 | 0 | 0 | 0 | 0 | 0 @@ -2289,7 +2921,10 @@ FROM (VALUES (0::numeric, 0::numeric), 433125 | 46375 | 875 | 875 | 875 | 875 43312.5 | 4637.5 | 87.5 | 87.5 | 87.5 | 87.5 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() @@ -2301,7 +2936,11 @@ FROM (VALUES (0::numeric, 0::numeric), (13272::numeric, 13272::numeric), (423282::numeric, 13272::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 ----------+-----------+--------------+--------------+--------------+-------------- 0 | 0 | 0 | 0 | 0 | 0 @@ -2311,7 +2950,10 @@ FROM (VALUES (0::numeric, 0::numeric), 423282 | 13272 | 11851896 | 11851896 | 11851896 | 11851896 42328.2 | 1327.2 | 1185189.6 | 1185189.6 | 1185189.6 | 1185189.6 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 ERROR: value overflows numeric format diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 432edfa063..13c91c9916 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -1872,7 +1872,7 @@ create temp table numerics( f_numeric numeric ); insert into numerics values -(0, '-infinity', '-infinity', '-1000'), -- numeric type lacks infinities +(0, '-infinity', '-infinity', '-infinity'), (1, -3, -3, -3), (2, -1, -1, -1), (3, 0, 0, 0), @@ -1880,7 +1880,7 @@ insert into numerics values (5, 1.12, 1.12, 1.12), (6, 2, 2, 2), (7, 100, 100, 100), -(8, 'infinity', 'infinity', '1000'), +(8, 'infinity', 'infinity', 'infinity'), (9, 'NaN', 'NaN', 'NaN'); select id, f_float4, first_value(id) over w, last_value(id) over w from numerics @@ -2078,7 +2078,7 @@ window w as (order by f_numeric range between 1 preceding and 1 following); id | f_numeric | first_value | last_value ----+-----------+-------------+------------ - 0 | -1000 | 0 | 0 + 0 | -Infinity | 0 | 0 1 | -3 | 1 | 1 2 | -1 | 2 | 3 3 | 0 | 2 | 3 @@ -2086,7 +2086,7 @@ window w as (order by f_numeric range between 5 | 1.12 | 4 | 6 6 | 2 | 4 | 6 7 | 100 | 7 | 7 - 8 | 1000 | 8 | 8 + 8 | Infinity | 8 | 8 9 | NaN | 9 | 9 (10 rows) @@ -2096,7 +2096,7 @@ window w as (order by f_numeric range between 1 preceding and 1.1::numeric following); id | f_numeric | first_value | last_value ----+-----------+-------------+------------ - 0 | -1000 | 0 | 0 + 0 | -Infinity | 0 | 0 1 | -3 | 1 | 1 2 | -1 | 2 | 3 3 | 0 | 2 | 4 @@ -2104,7 +2104,7 @@ window w as (order by f_numeric range between 5 | 1.12 | 4 | 6 6 | 2 | 4 | 6 7 | 100 | 7 | 7 - 8 | 1000 | 8 | 8 + 8 | Infinity | 8 | 8 9 | NaN | 9 | 9 (10 rows) @@ -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); ^ 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 from numerics window w as (order by f_numeric range between diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql index 044d515507..54f5cf7ecc 100644 --- a/src/test/regress/sql/aggregates.sql +++ b/src/test/regress/sql/aggregates.sql @@ -53,6 +53,8 @@ SELECT var_pop('nan'::float4), var_samp('nan'::float4); SELECT stddev_pop('nan'::float4), stddev_samp('nan'::float4); SELECT var_pop(1.0::numeric), var_samp(2.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 stddev_pop('nan'::numeric), stddev_samp('nan'::numeric); @@ -69,14 +71,26 @@ select sum('NaN'::numeric) from generate_series(1,3); select avg('NaN'::numeric) from generate_series(1,3); -- 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); -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); -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 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); +SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) +FROM (VALUES ('-infinity'), ('-infinity')) v(x); -- test accuracy with a large input offset SELECT avg(x::float8), var_pop(x::float8) diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 5dc80f686f..416c16722a 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -634,6 +634,119 @@ SELECT t1.id1, t1.result, t2.expected WHERE t1.id1 = t2.id 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... -- ****************************** @@ -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 (7, '0.00001'); 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; DROP TABLE fract_only; @@ -659,9 +775,25 @@ DROP TABLE fract_only; SELECT 'NaN'::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 '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 CREATE TABLE ceil_floor_round (a numeric); @@ -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('NaN', 3.0, 4.0, 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 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; 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; +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_25, 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'); 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 (' 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 INSERT INTO num_input_test(n1) VALUES (' '); @@ -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 (''); INSERT INTO num_input_test(n1) VALUES (' N aN '); +INSERT INTO num_input_test(n1) VALUES ('+ infinity'); SELECT * FROM num_input_test; @@ -952,6 +1118,9 @@ select 1.234 ^ 5678; select exp(0.0); select exp(1.0); 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 select exp(32.999); @@ -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('nan'::numeric, 100::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 select (i / (10::numeric ^ 131071))::numeric(1,0) from generate_series(6 * (10::numeric ^ 131071), @@ -1040,6 +1212,7 @@ select log(3.1954752e47, 9.4792021e-73); -- select scale(numeric 'NaN'); +select scale(numeric 'inf'); select scale(NULL::numeric); select scale(1.12); select scale(0); @@ -1054,6 +1227,7 @@ select scale(-13.000000000000000); -- 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.00); -- no digits again select min_scale(1.0); -- no scale @@ -1070,6 +1244,7 @@ select min_scale(1e100); -- very big number -- select trim_scale(numeric 'NaN'); +select trim_scale(numeric 'inf'); select trim_scale(1.120); select trim_scale(0); select trim_scale(0.00); @@ -1096,7 +1271,11 @@ FROM (VALUES (0::numeric, 0::numeric), (0::numeric, 46375::numeric), (433125::numeric, 46375::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() @@ -1108,7 +1287,11 @@ FROM (VALUES (0::numeric, 0::numeric), (13272::numeric, 13272::numeric), (423282::numeric, 13272::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 diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index 51ec0bac9a..af206ca466 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -499,7 +499,7 @@ create temp table numerics( ); insert into numerics values -(0, '-infinity', '-infinity', '-1000'), -- numeric type lacks infinities +(0, '-infinity', '-infinity', '-infinity'), (1, -3, -3, -3), (2, -1, -1, -1), (3, 0, 0, 0), @@ -507,7 +507,7 @@ insert into numerics values (5, 1.12, 1.12, 1.12), (6, 2, 2, 2), (7, 100, 100, 100), -(8, 'infinity', 'infinity', '1000'), +(8, 'infinity', 'infinity', 'infinity'), (9, 'NaN', 'NaN', 'NaN'); 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 1 preceding and 1.1::float8 following); -- currently unsupported 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); +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 1.1 preceding and 'NaN' following); -- error, NaN disallowed
pgsql-hackers by date: