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:

Previous
From: Peter Geoghegan
Date:
Subject: Re: Using Valgrind to detect faulty buffer accesses (no pin or buffer content lock held)
Next
From: Tom Lane
Date:
Subject: Re: v13 planner ERROR: could not determine which collation to use for string comparison