Обсуждение: Infinities in type numeric

Поиск
Список
Период
Сортировка

Infinities in type numeric

От
Tom Lane
Дата:
We had a discussion recently about how it'd be a good idea to support
infinity values in type numeric [1].  Here's a draft patch enabling
that, using the idea suggested in that thread of commandeering some
unused bits in the representation of numeric NaNs.  AFAICT we've been
careful to ensure those bits are always zero, so that this will work
without creating any pg_upgrade problems.

This is just WIP, partly because I haven't touched the SGML docs
yet, but also because there are some loose ends to be resolved:

* I believe I made all the functions that correspond to POSIX-standard
functions do what POSIX says for infinite inputs.  However, this does
not always match what our existing float8 functions do [2].  I'm
assuming that we'll change those functions to match POSIX; but if we
don't, this might need another look.

* I had to invent some semantics for non-standardized functions,
particularly numeric_mod, numeric_gcd, numeric_lcm.  This area
could use review to be sure that I chose desirable behaviors.

* I'm only about 50% sure that I understand what the sort abbreviation
code is doing.  A quick look from Peter or some other expert would be
helpful.

* It seems to me that the existing behavior of numeric_stddev_internal
is not quite right for the case of a single input value that is a NaN,
when in "sample" mode.  Per the comment "Sample stddev and variance are
undefined when N <= 1", ISTM that we ought to return NULL in this case,
but actually you get a NaN because the check for "NaNcount > 0" is made
before considering that.  I think that's the wrong way round --- in some
sense NULL is "less defined" than NaN, so that's what we ought to use.
Moreover, the float8 stddev code agrees: in HEAD you get

regression=# SELECT stddev_samp('nan'::float8);
 stddev_samp
-------------

(1 row)

regression=# SELECT stddev_samp('nan'::numeric);
 stddev_samp
-------------
         NaN
(1 row)

So I think we ought to make the numeric code match the former, and have
done that here.  However, the float8 code has its own issues for the
population case [3], and depending on what we do about that, this might
need further changes to agree.  (There's also the question of whether to
back-patch any such bug fixes.)

* The jsonpath code is inconsistent about how it handles NaN vs Inf [4].
I'm assuming here that we'll fix that by rejecting NaNs in that code,
but if we conclude that we do need to allow non-finite double values
there, probably we need to allow Infs too.

* It seems like there might be a use-case for isfinite() and maybe
isnan() SQL functions.  On the other hand, we don't have those for
float4/float8 either.  These could be a follow-on addition, anyway.

I'll stick this in the queue for review.

            regards, tom lane

[1] https://www.postgresql.org/message-id/27490.1590414212%40sss.pgh.pa.us
[2] https://www.postgresql.org/message-id/flat/582552.1591917752%40sss.pgh.pa.us
[3] https://www.postgresql.org/message-id/353062.1591898766%40sss.pgh.pa.us
[4] https://www.postgresql.org/message-id/flat/203949.1591879542%40sss.pgh.pa.us

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/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 553e261ed0..94b293d453 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -108,14 +108,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
@@ -167,25 +166,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.
  */
@@ -201,7 +222,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
@@ -209,7 +236,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 \
@@ -226,7 +255,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
@@ -273,7 +304,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 */
@@ -359,10 +390,14 @@ typedef struct NumericSumAccum
 #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_MIN)
+#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_MIN)
+#define NUMERIC_ABBREV_NINF         NumericAbbrevGetDatum(PG_INT32_MAX)
 #endif


@@ -378,6 +413,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};
@@ -415,6 +453,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
@@ -464,6 +508,7 @@ 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);

@@ -493,6 +538,7 @@ static int    cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
                            int var1weight, int var1sign,
                            const NumericDigit *var2digits, int var2ndigits,
                            int var2weight, int var2sign);
+static bool numeric_is_integral(Numeric num);
 static void add_var(const NumericVar *var1, const NumericVar *var2,
                     NumericVar *result);
 static void sub_var(const NumericVar *var1, const NumericVar *var2,
@@ -585,23 +631,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
     {
@@ -618,7 +684,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)
         {
@@ -634,6 +700,19 @@ 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++;
     }

     PG_RETURN_NUMERIC(res);
@@ -653,10 +732,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.
@@ -679,6 +765,17 @@ 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_maximum_size() -
  *
@@ -730,10 +827,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);

@@ -758,10 +862,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);

@@ -821,7 +932,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")));
@@ -847,11 +960,17 @@ 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.
      */
-    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);
     free_var(&value);
@@ -959,21 +1078,18 @@ numeric        (PG_FUNCTION_ARGS)
     NumericVar    var;

     /*
-     * Handle NaN
+     * Handle NaN and infinities.  Note that we allow these to be stored
+     * regardless of the precision limit; perhaps that should be restricted?
      */
-    if (NUMERIC_IS_NAN(num))
-        PG_RETURN_NUMERIC(make_result(&const_nan));
+    if (NUMERIC_IS_SPECIAL(num))
+        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
@@ -995,8 +1111,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)
@@ -1097,21 +1212,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);

@@ -1125,24 +1239,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))
@@ -1164,12 +1279,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;
 }

 /*
@@ -1182,37 +1327,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;
 }


@@ -1232,10 +1365,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
@@ -1281,10 +1414,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
@@ -1326,8 +1459,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);
@@ -1351,8 +1487,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);
@@ -1388,26 +1527,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);

@@ -1508,12 +1667,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);
@@ -1717,9 +1885,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
     {
@@ -1845,7 +2018,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;
@@ -2148,20 +2321,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
     {
@@ -2188,10 +2383,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")));
@@ -2212,6 +2409,45 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
     {
         result = less;            /* non-NAN < NAN */
     }
+
+    /*
+     * Deal with infinite offset (necessarily +Inf, at this point).  Infinite
+     * val and/or base cannot affect the conclusion.
+     */
+    else if (NUMERIC_IS_SPECIAL(offset))
+    {
+        Assert(NUMERIC_IS_PINF(offset));
+        result = (sub ? !less : less);
+    }
+
+    /*
+     * 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
     {
         /*
@@ -2262,8 +2498,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);
@@ -2343,7 +2579,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);
@@ -2427,10 +2664,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.
@@ -2483,10 +2742,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.
@@ -2539,10 +2820,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.
@@ -2603,10 +2938,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
@@ -2658,11 +3050,58 @@ numeric_div_trunc(PG_FUNCTION_ARGS)
     NumericVar    result;
     Numeric        res;

-    /*
-     * Handle NaN
-     */
-    if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
-        PG_RETURN_NUMERIC(make_result(&const_nan));
+    /*
+     * Handle NaN and infinities
+     */
+    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
@@ -2721,8 +3160,32 @@ 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
+     */
+    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);
@@ -2761,10 +3224,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
@@ -2848,10 +3311,30 @@ numeric_gcd(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));
+        /*----------
+         * We have these cases involving infinities:
+         *        gcd([-]Inf, 0) = Inf
+         *        gcd([-]Inf, [-]Inf) = Inf
+         *        gcd([-]Inf, x) = abs(x) for any finite nonzero x
+         *----------
+         */
+        if (NUMERIC_IS_INF(num1))
+        {
+            if (numeric_sign_internal(num2) == 0)
+                PG_RETURN_NUMERIC(make_result(&const_pinf));
+            return DirectFunctionCall1(numeric_abs, NumericGetDatum(num2));
+        }
+        Assert(NUMERIC_IS_INF(num2));
+        if (numeric_sign_internal(num1) == 0)
+            PG_RETURN_NUMERIC(make_result(&const_pinf));
+        return DirectFunctionCall1(numeric_abs, NumericGetDatum(num1));
+    }

     /*
      * Unpack the arguments
@@ -2890,10 +3373,23 @@ numeric_lcm(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));
+
+        /*
+         * The lcm for any case involving an infinity is +Inf ... except when
+         * the other argument is zero.
+         */
+        if (numeric_sign_internal(num1) == 0 ||
+            numeric_sign_internal(num2) == 0)
+            PG_RETURN_NUMERIC(make_result(&const_zero));
+        else
+            PG_RETURN_NUMERIC(make_result(&const_pinf));
+    }

     /*
      * Unpack the arguments
@@ -2997,10 +3493,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
@@ -3048,10 +3552,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
@@ -3109,10 +3619,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);
@@ -3151,10 +3668,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
@@ -3180,7 +3726,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)
@@ -3190,60 +3736,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.
@@ -3253,7 +3909,6 @@ numeric_power(PG_FUNCTION_ARGS)
     res = make_result(&result);

     free_var(&result);
-    free_var(&arg2_trunc);

     PG_RETURN_NUMERIC(res);
 }
@@ -3268,7 +3923,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));
@@ -3335,7 +3990,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);
@@ -3355,8 +4010,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);
@@ -3403,7 +4058,7 @@ numeric_int4_opt_error(Numeric num, bool *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)
         {
@@ -3412,9 +4067,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")));
         }
     }

@@ -3494,10 +4154,17 @@ numeric_int8(PG_FUNCTION_ARGS)
     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);
@@ -3539,10 +4206,17 @@ numeric_int2(PG_FUNCTION_ARGS)
     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);
@@ -3577,9 +4251,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);

@@ -3603,8 +4280,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)));
@@ -3628,6 +4312,7 @@ numeric_float8_no_overflow(PG_FUNCTION_ARGS)
     Numeric        num = PG_GETARG_NUMERIC(0);
     double        val;

+    /* numeric_to_double_no_overflow will handle infinities */
     if (NUMERIC_IS_NAN(num))
         PG_RETURN_FLOAT8(get_float8_nan());

@@ -3648,9 +4333,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);

@@ -3674,8 +4362,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)));
@@ -3711,9 +4406,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.
@@ -3765,10 +4466,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;
     }

@@ -3830,10 +4536,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;
     }

@@ -3956,6 +4667,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;

@@ -3969,6 +4682,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)
     {
@@ -4044,6 +4759,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;

@@ -4056,6 +4773,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)
     {
@@ -4134,6 +4853,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);
@@ -4188,6 +4913,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);

@@ -4256,6 +4987,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);
@@ -4319,6 +5056,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);

@@ -5111,12 +5854,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);
@@ -5137,12 +5888,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);
@@ -5172,21 +5931,36 @@ numeric_stddev_internal(NumericAggState *state,
                 vsumX,
                 vsumX2,
                 vNminus1;
-    const NumericVar *comp;
+    int64        totCount;
     int            rscale;

-    /* Deal with empty input and NaN-input cases */
-    if (state == NULL || (state->N + state->NaNcount) == 0)
+    /*
+     * Sample stddev and variance are undefined when N <= 1; population stddev
+     * is undefined when N == 0.  Return NULL in either case (note that NaNs
+     * and infinities count as normal inputs for this purpose).
+     */
+    if (state == NULL || (totCount = NA_TOTAL_COUNT(state)) == 0)
+    {
+        *is_null = true;
+        return NULL;
+    }
+
+    if (sample && totCount <= 1)
     {
         *is_null = true;
         return NULL;
     }

+    /*
+     * Deal with NaN and infinity cases.  By analogy to the behavior of the
+     * float8 functions, any infinity input produces NaN output.
+     */
     *is_null = false;

-    if (state->NaNcount > 0)
+    if (state->NaNcount > 0 || state->pInfcount > 0 || state->nInfcount > 0)
         return make_result(&const_nan);

+    /* OK, normal calculation applies */
     init_var(&vN);
     init_var(&vsumX);
     init_var(&vsumX2);
@@ -5195,21 +5969,6 @@ numeric_stddev_internal(NumericAggState *state,
     accum_sum_final(&(state->sumX), &vsumX);
     accum_sum_final(&(state->sumX2), &vsumX2);

-    /*
-     * Sample stddev and variance are undefined when N <= 1; population stddev
-     * is undefined when N == 0. Return NULL in either case.
-     */
-    if (sample)
-        comp = &const_one;
-    else
-        comp = &const_zero;
-
-    if (cmp_var(&vN, comp) <= 0)
-    {
-        *is_null = true;
-        return NULL;
-    }
-
     init_var(&vNminus1);
     sub_var(&vN, &const_one, &vNminus1);

@@ -5841,6 +6600,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;
@@ -5872,6 +6637,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;
@@ -6426,6 +7197,19 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
 }


+/*
+ * duplicate_numeric() - copy a packed-format Numeric
+ */
+static Numeric
+duplicate_numeric(Numeric num)
+{
+    Numeric        res;
+
+    res = (Numeric) palloc(VARSIZE(num));
+    memcpy(res, num, VARSIZE(num));
+    return res;
+}
+
 /*
  * make_result_opt_error() -
  *
@@ -6447,12 +7231,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);
@@ -6861,6 +7655,9 @@ int128_to_numericvar(int128 val, NumericVar *var)

 /*
  * Convert numeric to float8; if out of range, return +/- HUGE_VAL
+ *
+ * Note the caller must already have dealt with NaN, but we can
+ * handle infinities here.
  */
 static double
 numeric_to_double_no_overflow(Numeric num)
@@ -6869,6 +7666,15 @@ numeric_to_double_no_overflow(Numeric num)
     double        val;
     char       *endptr;

+    if (NUMERIC_IS_SPECIAL(num))
+    {
+        Assert(!NUMERIC_IS_NAN(num));
+        if (NUMERIC_IS_PINF(num))
+            return HUGE_VAL;
+        else
+            return -HUGE_VAL;
+    }
+
     tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
                                               NumericGetDatum(num)));

@@ -6972,6 +7778,28 @@ cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
                           var1digits, var1ndigits, var1weight);
 }

+/*
+ * Test whether a Numeric value is 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);
+}
+

 /*
  * add_var() -
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 d659013e41..ea6f6f1e22 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -201,32 +201,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 c7fe63d037..2b97ff5a21 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -660,6 +660,429 @@ 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)
+
+WITH v(x) AS
+  (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),
+         ('inf'),('-inf'),('nan'),
+         ('inf'),('-inf'),('nan'))
+SELECT x
+FROM v ORDER BY x;
+     x
+-----------
+ -Infinity
+ -Infinity
+    -7.777
+        -1
+         0
+         1
+       4.2
+  Infinity
+  Infinity
+       NaN
+       NaN
+(11 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...
 -- ******************************
@@ -716,9 +1139,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 +1175,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 +1293,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 +1704,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 +1985,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 +2024,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 +2380,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 +2469,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 +2680,12 @@ select scale(numeric 'NaN');

 (1 row)

+select scale(numeric 'inf');
+ scale
+-------
+
+(1 row)
+
 select scale(NULL::numeric);
  scale
 -------
@@ -2138,6 +2743,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 +2818,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,16 +2897,23 @@ 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);
-    a     |     b     |   gcd   |   gcd   |   gcd   |   gcd
-----------+-----------+---------+---------+---------+---------
-        0 |         0 |       0 |       0 |       0 |       0
-        0 |       NaN |     NaN |     NaN |     NaN |     NaN
-        0 |     46375 |   46375 |   46375 |   46375 |   46375
-   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)
+             (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
+        0 |       NaN |      NaN |      NaN |      NaN |      NaN
+        0 |     46375 |    46375 |    46375 |    46375 |    46375
+   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
+ Infinity |         0 | Infinity | Infinity | Infinity | Infinity
+ Infinity |        42 |       42 |       42 |       42 |       42
+ Infinity |  Infinity | Infinity | Infinity | Infinity | Infinity
+(9 rows)

 --
 -- Tests for LCM()
@@ -2301,7 +2925,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 +2939,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 |            0 |            0 |            0 |            0
+ Infinity |        42 |     Infinity |     Infinity |     Infinity |     Infinity
+ Infinity |  Infinity |     Infinity |     Infinity |     Infinity |     Infinity
+(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 d5fd4045f9..2512852a94 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
@@ -2006,7 +2006,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
@@ -2014,7 +2014,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)

@@ -2024,7 +2024,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
@@ -2032,7 +2032,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)

@@ -2044,6 +2044,24 @@ 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
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 2a066f5a3a..f02ec6ab05 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -55,14 +55,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 41475a9a24..530d17aa91 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -634,6 +634,118 @@ 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;
+
+WITH v(x) AS
+  (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),
+         ('inf'),('-inf'),('nan'),
+         ('inf'),('-inf'),('nan'))
+SELECT x
+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...
 -- ******************************
@@ -659,9 +771,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 +825,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 +913,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 +994,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 +1010,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 +1114,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 +1138,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 +1208,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 +1223,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 +1240,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 +1267,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,6 +1283,10 @@ 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 fe273aa31e..b4e7f6d8c6 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
@@ -558,6 +558,10 @@ 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
              1.1 preceding and 'NaN' following);  -- error, NaN disallowed


Re: Infinities in type numeric

От
Andrew Gierth
Дата:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 Tom> * I'm only about 50% sure that I understand what the sort
 Tom> abbreviation code is doing. A quick look from Peter or some other
 Tom> expert would be helpful.

That code was originally mine, so I'll look at it.

-- 
Andrew (irc:RhodiumToad)



Re: Infinities in type numeric

От
Andrew Gierth
Дата:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 Tom> @@ -359,10 +390,14 @@ typedef struct NumericSumAccum
 Tom>  #define NumericAbbrevGetDatum(X) ((Datum) (X))
 Tom>  #define DatumGetNumericAbbrev(X) ((int64) (X))
 Tom>  #define NUMERIC_ABBREV_NAN         NumericAbbrevGetDatum(PG_INT64_MIN)
 Tom> +#define NUMERIC_ABBREV_PINF         NumericAbbrevGetDatum(PG_INT64_MIN)
 Tom> +#define NUMERIC_ABBREV_NINF         NumericAbbrevGetDatum(PG_INT64_MAX)
 Tom>  #else
 Tom>  #define NumericAbbrevGetDatum(X) ((Datum) (X))
 Tom>  #define DatumGetNumericAbbrev(X) ((int32) (X))
 Tom>  #define NUMERIC_ABBREV_NAN         NumericAbbrevGetDatum(PG_INT32_MIN)
 Tom> +#define NUMERIC_ABBREV_PINF         NumericAbbrevGetDatum(PG_INT32_MIN)
 Tom> +#define NUMERIC_ABBREV_NINF         NumericAbbrevGetDatum(PG_INT32_MAX)
 Tom>  #endif

I'd have been more inclined to go with -PG_INT64_MAX / -PG_INT32_MAX for
the NUMERIC_ABBREV_PINF value. It seems more likely to be beneficial to
bucket +Inf and NaN separately (and put +Inf in with the "too large to
abbreviate" values) than to bucket them together so as to distinguish
between +Inf and "too large" values. But this is an edge case in any
event, so it probably wouldn't make a great deal of difference unless
you're sorting on data with a large proportion of both +Inf and NaN
values.

(It would be possible to add another bucket so that "too large", +Inf,
and NaN were three separate buckets, but honestly any more complexity
seems not worth it for handling an edge case.)

The actual changes to the abbrev stuff look fine to me.

-- 
Andrew (irc:RhodiumToad)



Re: Infinities in type numeric

От
Tom Lane
Дата:
Andrew Gierth <andrew@tao11.riddles.org.uk> writes:
> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:
>  Tom> +#define NUMERIC_ABBREV_PINF         NumericAbbrevGetDatum(PG_INT64_MIN)
>  Tom> +#define NUMERIC_ABBREV_PINF         NumericAbbrevGetDatum(PG_INT32_MIN)

> I'd have been more inclined to go with -PG_INT64_MAX / -PG_INT32_MAX for
> the NUMERIC_ABBREV_PINF value. It seems more likely to be beneficial to
> bucket +Inf and NaN separately (and put +Inf in with the "too large to
> abbreviate" values) than to bucket them together so as to distinguish
> between +Inf and "too large" values. But this is an edge case in any
> event, so it probably wouldn't make a great deal of difference unless
> you're sorting on data with a large proportion of both +Inf and NaN
> values.

I had been worried about things possibly sorting in the wrong order
if I did that.  However, now that I look more closely I see that

 * We convert the absolute value of the numeric to a 31-bit or 63-bit positive
 * value, and then negate it if the original number was positive.

so that a finite value should never map to INT[64]_MIN, making it
safe to do as you suggest.  I agree that distinguishing +Inf from NaN
is probably more useful than distinguishing it from the very largest
class of finite values, so will do it as you suggest.  Thanks!

            regards, tom lane



Re: Infinities in type numeric

От
Robert Haas
Дата:
On Thu, Jun 11, 2020 at 9:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> We had a discussion recently about how it'd be a good idea to support
> infinity values in type numeric [1].

Only a minority of that discussion was actually on that topic, and I'm
not sure there was a clear consensus in favor of it.

FWIW, I don't particularly like the idea. Back when I was an
application developer, I remember having to insert special cases into
any code that dealt with double precision to deal with +/-Inf and NaN.
I was happy that I didn't need them for numeric, too. So this change
would have made me sad.

It's possible I'm the only one, though.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Infinities in type numeric

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Thu, Jun 11, 2020 at 9:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> We had a discussion recently about how it'd be a good idea to support
>> infinity values in type numeric [1].

> FWIW, I don't particularly like the idea. Back when I was an
> application developer, I remember having to insert special cases into
> any code that dealt with double precision to deal with +/-Inf and NaN.
> I was happy that I didn't need them for numeric, too. So this change
> would have made me sad.

Well, you're already stuck with special-casing numeric NaN, so I'm
not sure that Inf makes your life noticeably worse on that score.

This does tie into something I have a question about in the patch's
comments though.  As the patch stands, numeric(numeric, integer)
(that is, the typmod-enforcement function) just lets infinities
through regardless of the typmod, on the grounds that it is/was also
letting NaNs through regardless of typmod.  But you could certainly
make the argument that Inf should only be allowed in an unconstrained
numeric column, because by definition it overflows any finite precision
restriction.  If we did that, you'd never see Inf in a
standard-conforming column, since SQL doesn't allow unconstrained
numeric columns IIRC.  That'd at least ameliorate your concern.

If we were designing this today, I think I'd vote to disallow NaN
in a constrained numeric column, too.  But I suppose it's far too
late to change that aspect.

            regards, tom lane



Re: Infinities in type numeric

От
Robert Haas
Дата:
On Fri, Jun 12, 2020 at 1:01 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> This does tie into something I have a question about in the patch's
> comments though.  As the patch stands, numeric(numeric, integer)
> (that is, the typmod-enforcement function) just lets infinities
> through regardless of the typmod, on the grounds that it is/was also
> letting NaNs through regardless of typmod.  But you could certainly
> make the argument that Inf should only be allowed in an unconstrained
> numeric column, because by definition it overflows any finite precision
> restriction.  If we did that, you'd never see Inf in a
> standard-conforming column, since SQL doesn't allow unconstrained
> numeric columns IIRC.  That'd at least ameliorate your concern.

Yes, I agree. It also seems like a more principled choice - I am not
sure why if I ask for a number no larger than 10^3 we ought to permit
infinity.

BTW, has there been any thought to supporting a negative scale for the
numeric data type? If you can cut off digits after the decimal, why
not before?

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Infinities in type numeric

От
Andrew Gierth
Дата:
>>>>> "Tom" == Tom Lane <tgl@sss.pgh.pa.us> writes:

 [...]
 Tom> so that a finite value should never map to INT[64]_MIN, making it
 Tom> safe to do as you suggest. I agree that distinguishing +Inf from
 Tom> NaN is probably more useful than distinguishing it from the very
 Tom> largest class of finite values, so will do it as you suggest.
 Tom> Thanks!

It would make sense to make sure there's a test case in which at least
one value of all three of: a finite value much greater than 10^332, a
+Inf, and a NaN were all present in the same sort, if there isn't one
already.

-- 
Andrew (irc:RhodiumToad)



Re: Infinities in type numeric

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> BTW, has there been any thought to supporting a negative scale for the
> numeric data type? If you can cut off digits after the decimal, why
> not before?

Hm, would there be any real use-case?

An implementation issue is that even in the "long" numeric format,
we cram dscale into a 14-bit unsigned field.  You could redefine
the field as signed and pray that nobody has dscales above 8K
stored on disk, but I'm dubious that there's a good argument for
taking that risk.

There might be algorithmic issues as well, haven't really looked.
Any such problems would probably be soluble, if need be by forcing
the scale to be at least 0 for calculation and then rounding
afterwards.

            regards, tom lane



Re: Infinities in type numeric

От
Robert Haas
Дата:
On Fri, Jun 12, 2020 at 2:14 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
> > BTW, has there been any thought to supporting a negative scale for the
> > numeric data type? If you can cut off digits after the decimal, why
> > not before?
>
> Hm, would there be any real use-case?

Compatibility... apparently people do use it.

> An implementation issue is that even in the "long" numeric format,
> we cram dscale into a 14-bit unsigned field.  You could redefine
> the field as signed and pray that nobody has dscales above 8K
> stored on disk, but I'm dubious that there's a good argument for
> taking that risk.

That doesn't sound too appealing I guess, but couldn't you enforce it
as a typemod without changing the on-disk representation of the
values?


-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Infinities in type numeric

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Fri, Jun 12, 2020 at 2:14 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Robert Haas <robertmhaas@gmail.com> writes:
>>> BTW, has there been any thought to supporting a negative scale for the
>>> numeric data type? If you can cut off digits after the decimal, why
>>> not before?

>> Hm, would there be any real use-case?

> Compatibility... apparently people do use it.

Uh, compatibility with what?  Not the SQL spec, for sure.

>> An implementation issue is that even in the "long" numeric format,
>> we cram dscale into a 14-bit unsigned field.

> That doesn't sound too appealing I guess, but couldn't you enforce it
> as a typemod without changing the on-disk representation of the
> values?

On second thought, I'm confusing two distinct though related concepts.
dscale is *display* scale, and it's fine that it's unsigned, because
there is no reason to suppress printing digits to the left of the decimal
point.  ("Whaddya mean, 10 is really 100?")  We could allow the "scale"
part of typmod to be negative and thereby cause an input of, say,
123.45 to be rounded to say 100 --- but it should display as 100 not 1,
so its display scale is still 0.

Hence, there's no pg_upgrade issue.  You'd still need to rethink how
precision and scale get packed into an int32 typmod, but those only
exist in catalog data, so pg_upgrade's schema dump/restore would be
enough to update them.

Having said that, we've long resisted redefining the encoding of
typmod for other data types (despite the clear insanity of some
of the encodings), for fear that client code might be looking at
those catalog columns.  I'm not sure how big a deal that really is.

            regards, tom lane



Re: Infinities in type numeric

От
Tom Lane
Дата:
Here's a v2 patch:

* Rebased over today's nearby commits

* Documentation changes added

* Sort abbrev support improved per Andrew's suggestions

* Infinities now considered to fail any typmod precision limit,
  per discussion with Robert.

            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 3df189ad85..a9ed269e15 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,39 @@ 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 generally used to
+     represent undefined calculational results.  With very few exceptions,
+     any operation with a <literal>NaN</literal> input yields
+     another <literal>NaN</literal>.
     </para>

     <note>
@@ -781,9 +812,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 +836,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 eea4239854..011b6b13a6 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -108,14 +108,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
@@ -167,25 +166,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.
  */
@@ -201,7 +222,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
@@ -209,7 +236,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 \
@@ -226,7 +255,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
@@ -273,7 +304,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 */
@@ -353,16 +384,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


@@ -378,6 +419,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};
@@ -415,6 +459,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
@@ -464,6 +514,7 @@ 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);

@@ -493,6 +544,7 @@ static int    cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
                            int var1weight, int var1sign,
                            const NumericDigit *var2digits, int var2ndigits,
                            int var2weight, int var2sign);
+static bool numeric_is_integral(Numeric num);
 static void add_var(const NumericVar *var1, const NumericVar *var2,
                     NumericVar *result);
 static void sub_var(const NumericVar *var1, const NumericVar *var2,
@@ -585,23 +637,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
     {
@@ -618,7 +690,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)
         {
@@ -634,6 +706,19 @@ 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++;
     }

     PG_RETURN_NUMERIC(res);
@@ -653,10 +738,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.
@@ -679,6 +771,17 @@ 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_maximum_size() -
  *
@@ -730,10 +833,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);

@@ -758,10 +868,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);

@@ -821,7 +938,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")));
@@ -847,11 +966,17 @@ 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.
      */
-    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);
     free_var(&value);
@@ -958,22 +1083,12 @@ numeric        (PG_FUNCTION_ARGS)
     int            maxdigits;
     NumericVar    var;

-    /*
-     * Handle NaN
-     */
-    if (NUMERIC_IS_NAN(num))
-        PG_RETURN_NUMERIC(make_result(&const_nan));
-
     /*
      * 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
@@ -983,6 +1098,23 @@ numeric        (PG_FUNCTION_ARGS)
     scale = tmp_typmod & 0xffff;
     maxdigits = precision - scale;

+    /*
+     * Handle NaN and infinities.  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_SPECIAL(num))
+    {
+        if (NUMERIC_IS_NAN(num))
+            PG_RETURN_NUMERIC(duplicate_numeric(num));
+        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)));
+    }
+
     /*
      * If the number is certainly in bounds and due to the target scale no
      * rounding could be necessary, just make a copy of the input and modify
@@ -995,8 +1127,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)
@@ -1097,21 +1228,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);

@@ -1125,24 +1255,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))
@@ -1164,12 +1295,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;
 }

 /*
@@ -1182,37 +1343,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;
 }


@@ -1232,10 +1381,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
@@ -1281,10 +1430,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
@@ -1326,8 +1475,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);
@@ -1351,8 +1503,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);
@@ -1388,26 +1543,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);

@@ -1508,12 +1683,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);
@@ -1717,9 +1901,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
     {
@@ -1845,7 +2034,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;
@@ -2148,20 +2337,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
     {
@@ -2188,10 +2399,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")));
@@ -2212,6 +2425,45 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
     {
         result = less;            /* non-NAN < NAN */
     }
+
+    /*
+     * Deal with infinite offset (necessarily +Inf, at this point).  Infinite
+     * val and/or base cannot affect the conclusion.
+     */
+    else if (NUMERIC_IS_SPECIAL(offset))
+    {
+        Assert(NUMERIC_IS_PINF(offset));
+        result = (sub ? !less : less);
+    }
+
+    /*
+     * 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
     {
         /*
@@ -2262,8 +2514,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);
@@ -2343,7 +2595,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);
@@ -2427,10 +2680,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.
@@ -2483,10 +2758,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.
@@ -2539,10 +2836,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.
@@ -2602,11 +2953,68 @@ numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
     if (have_error)
         *have_error = false;

-    /*
-     * Handle NaN
-     */
-    if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
-        return make_result(&const_nan);
+    /*
+     * Handle NaN and infinities
+     */
+    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
@@ -2659,10 +3067,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
@@ -2721,8 +3176,32 @@ 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
+     */
+    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);
@@ -2761,10 +3240,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
@@ -2848,10 +3327,30 @@ numeric_gcd(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));
+        /*----------
+         * We have these cases involving infinities:
+         *        gcd([-]Inf, 0) = Inf
+         *        gcd([-]Inf, [-]Inf) = Inf
+         *        gcd([-]Inf, x) = abs(x) for any finite nonzero x
+         *----------
+         */
+        if (NUMERIC_IS_INF(num1))
+        {
+            if (numeric_sign_internal(num2) == 0)
+                PG_RETURN_NUMERIC(make_result(&const_pinf));
+            return DirectFunctionCall1(numeric_abs, NumericGetDatum(num2));
+        }
+        Assert(NUMERIC_IS_INF(num2));
+        if (numeric_sign_internal(num1) == 0)
+            PG_RETURN_NUMERIC(make_result(&const_pinf));
+        return DirectFunctionCall1(numeric_abs, NumericGetDatum(num1));
+    }

     /*
      * Unpack the arguments
@@ -2890,10 +3389,23 @@ numeric_lcm(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));
+
+        /*
+         * The lcm for any case involving an infinity is +Inf ... except when
+         * the other argument is zero.
+         */
+        if (numeric_sign_internal(num1) == 0 ||
+            numeric_sign_internal(num2) == 0)
+            PG_RETURN_NUMERIC(make_result(&const_zero));
+        else
+            PG_RETURN_NUMERIC(make_result(&const_pinf));
+    }

     /*
      * Unpack the arguments
@@ -2997,10 +3509,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
@@ -3048,10 +3568,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
@@ -3109,10 +3635,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);
@@ -3151,10 +3684,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
@@ -3180,7 +3742,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)
@@ -3190,60 +3752,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.
@@ -3253,7 +3925,6 @@ numeric_power(PG_FUNCTION_ARGS)
     res = make_result(&result);

     free_var(&result);
-    free_var(&arg2_trunc);

     PG_RETURN_NUMERIC(res);
 }
@@ -3268,7 +3939,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));
@@ -3335,7 +4006,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);
@@ -3355,8 +4026,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);
@@ -3403,7 +4074,7 @@ numeric_int4_opt_error(Numeric num, bool *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)
         {
@@ -3412,9 +4083,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")));
         }
     }

@@ -3494,10 +4170,17 @@ numeric_int8(PG_FUNCTION_ARGS)
     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);
@@ -3539,10 +4222,17 @@ numeric_int2(PG_FUNCTION_ARGS)
     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);
@@ -3577,9 +4267,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);

@@ -3603,8 +4296,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)));
@@ -3628,6 +4328,7 @@ numeric_float8_no_overflow(PG_FUNCTION_ARGS)
     Numeric        num = PG_GETARG_NUMERIC(0);
     double        val;

+    /* numeric_to_double_no_overflow will handle infinities */
     if (NUMERIC_IS_NAN(num))
         PG_RETURN_FLOAT8(get_float8_nan());

@@ -3648,9 +4349,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);

@@ -3674,8 +4378,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)));
@@ -3711,9 +4422,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.
@@ -3765,10 +4482,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;
     }

@@ -3830,10 +4552,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;
     }

@@ -3956,6 +4683,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;

@@ -3969,6 +4698,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)
     {
@@ -4044,6 +4775,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;

@@ -4056,6 +4789,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)
     {
@@ -4134,6 +4869,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);
@@ -4188,6 +4929,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);

@@ -4256,6 +5003,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);
@@ -4319,6 +5072,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);

@@ -5111,12 +5870,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);
@@ -5137,12 +5904,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);
@@ -5178,9 +5953,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;
@@ -5195,9 +5970,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 */
@@ -5840,6 +6616,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;
@@ -5871,6 +6653,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;
@@ -6425,6 +7213,19 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
 }


+/*
+ * duplicate_numeric() - copy a packed-format Numeric
+ */
+static Numeric
+duplicate_numeric(Numeric num)
+{
+    Numeric        res;
+
+    res = (Numeric) palloc(VARSIZE(num));
+    memcpy(res, num, VARSIZE(num));
+    return res;
+}
+
 /*
  * make_result_opt_error() -
  *
@@ -6446,12 +7247,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);
@@ -6860,6 +7671,9 @@ int128_to_numericvar(int128 val, NumericVar *var)

 /*
  * Convert numeric to float8; if out of range, return +/- HUGE_VAL
+ *
+ * Note the caller must already have dealt with NaN, but we can
+ * handle infinities here.
  */
 static double
 numeric_to_double_no_overflow(Numeric num)
@@ -6868,6 +7682,15 @@ numeric_to_double_no_overflow(Numeric num)
     double        val;
     char       *endptr;

+    if (NUMERIC_IS_SPECIAL(num))
+    {
+        Assert(!NUMERIC_IS_NAN(num));
+        if (NUMERIC_IS_PINF(num))
+            return HUGE_VAL;
+        else
+            return -HUGE_VAL;
+    }
+
     tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
                                               NumericGetDatum(num)));

@@ -6971,6 +7794,28 @@ cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
                           var1digits, var1ndigits, var1weight);
 }

+/*
+ * Test whether a Numeric value is 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);
+}
+

 /*
  * add_var() -
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 c7fe63d037..9e87678012 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,16 +2908,23 @@ 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);
-    a     |     b     |   gcd   |   gcd   |   gcd   |   gcd
-----------+-----------+---------+---------+---------+---------
-        0 |         0 |       0 |       0 |       0 |       0
-        0 |       NaN |     NaN |     NaN |     NaN |     NaN
-        0 |     46375 |   46375 |   46375 |   46375 |   46375
-   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)
+             (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
+        0 |       NaN |      NaN |      NaN |      NaN |      NaN
+        0 |     46375 |    46375 |    46375 |    46375 |    46375
+   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
+ Infinity |         0 | Infinity | Infinity | Infinity | Infinity
+ Infinity |        42 |       42 |       42 |       42 |       42
+ Infinity |  Infinity | Infinity | Infinity | Infinity | Infinity
+(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 |            0 |            0 |            0 |            0
+ Infinity |        42 |     Infinity |     Infinity |     Infinity |     Infinity
+ Infinity |  Infinity |     Infinity |     Infinity |     Infinity |     Infinity
+(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 d5fd4045f9..2512852a94 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
@@ -2006,7 +2006,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
@@ -2014,7 +2014,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)

@@ -2024,7 +2024,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
@@ -2032,7 +2032,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)

@@ -2044,6 +2044,24 @@ 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
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 41475a9a24..9091087ba3 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,6 +1287,10 @@ 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 fe273aa31e..b4e7f6d8c6 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
@@ -558,6 +558,10 @@ 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
              1.1 preceding and 'NaN' following);  -- error, NaN disallowed


Re: Infinities in type numeric

От
Dean Rasheed
Дата:
On Fri, 12 Jun 2020 at 02:16, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> * I had to invent some semantics for non-standardized functions,
> particularly numeric_mod, numeric_gcd, numeric_lcm.  This area
> could use review to be sure that I chose desirable behaviors.
>

I think the semantics you've chosen for numeric_mod() are reasonable,
and I think they're consistent with POSIX fmod().

However, it looks like you've chosen gcd('Inf', x) = x, whereas I'd
say that the result should be 'NaN'.

One way to look at it is that the GCD result should exactly divide
both inputs with no remainder, but the remainder when you divide 'Inf'
by x is undefined, so you can't say that x exactly divides 'Inf'.

Another way to look at it is that gcd('Inf', x) is limit(n -> 'Inf',
gcd(n, x)), but that limit isn't well-defined. For example, suppose
x=10, then gcd('Inf', 10) = limit(n -> 'Inf', gcd(n, 10)), but gcd(n,
10) is either 1,2,5 or 10 depending on n, and it does not converge to
any particular value in the limit n -> 'Inf'.

A third way to look at it would be to apply one round of Euclid's
algorithm to it: gcd('Inf', x) = gcd(x, mod('Inf', x)) = gcd(x, 'NaN')
= 'NaN'.

Now you could argue that x=0 is a special case, and gcd('Inf', 0) =
'Inf' on the grounds that gcd(a, 0) = a for all finite 'a'. However, I
don't think that's particularly useful, and it fails the first test
that the result exactly divides both inputs because mod('Inf', 'Inf')
is undefined ('NaN').

Similar arguments apply to lcm(), so I'd say that both gcd() and lcm()
should return 'NaN' if either input is 'Inf' or 'NaN'.

Regards,
Dean



Re: Infinities in type numeric

От
Vik Fearing
Дата:
On 6/12/20 7:00 PM, Tom Lane wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Thu, Jun 11, 2020 at 9:16 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> We had a discussion recently about how it'd be a good idea to support
>>> infinity values in type numeric [1].
> 
>> FWIW, I don't particularly like the idea. Back when I was an
>> application developer, I remember having to insert special cases into
>> any code that dealt with double precision to deal with +/-Inf and NaN.
>> I was happy that I didn't need them for numeric, too. So this change
>> would have made me sad.
> 
> Well, you're already stuck with special-casing numeric NaN, so I'm
> not sure that Inf makes your life noticeably worse on that score.
> 
> This does tie into something I have a question about in the patch's
> comments though.  As the patch stands, numeric(numeric, integer)
> (that is, the typmod-enforcement function) just lets infinities
> through regardless of the typmod, on the grounds that it is/was also
> letting NaNs through regardless of typmod.  But you could certainly
> make the argument that Inf should only be allowed in an unconstrained
> numeric column, because by definition it overflows any finite precision
> restriction.  If we did that, you'd never see Inf in a
> standard-conforming column, since SQL doesn't allow unconstrained
> numeric columns IIRC.


It does.  The precision and scale are both optional.

If the precision is missing, it's implementation defined; if the scale
is missing, it's 0.
-- 
Vik Fearing



Re: Infinities in type numeric

От
Tom Lane
Дата:
Vik Fearing <vik@postgresfriends.org> writes:
> On 6/12/20 7:00 PM, Tom Lane wrote:
>> If we did that, you'd never see Inf in a
>> standard-conforming column, since SQL doesn't allow unconstrained
>> numeric columns IIRC.

> It does.  The precision and scale are both optional.
> If the precision is missing, it's implementation defined; if the scale
> is missing, it's 0.

Ah, right, the way in which we deviate from the spec is that an
unconstrained numeric column doesn't coerce every entry to scale 0.

Still, that *is* a spec deviation, so adding "... and it allows Inf"
doesn't seem like it's making things worse for spec-compliant apps.

            regards, tom lane



Re: Infinities in type numeric

От
Tom Lane
Дата:
Dean Rasheed <dean.a.rasheed@gmail.com> writes:
> On Fri, 12 Jun 2020 at 02:16, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> * I had to invent some semantics for non-standardized functions,
>> particularly numeric_mod, numeric_gcd, numeric_lcm.  This area
>> could use review to be sure that I chose desirable behaviors.

> I think the semantics you've chosen for numeric_mod() are reasonable,
> and I think they're consistent with POSIX fmod().

Ah, I had not thought to look at fmod().  I see that POSIX treats
x-is-infinite the same as y-is-zero: raise EDOM and return NaN.
I think it's okay to deviate to the extent of throwing an error in
one case and returning NaN in the other, but I added a comment
noting the difference.

> Similar arguments apply to lcm(), so I'd say that both gcd() and lcm()
> should return 'NaN' if either input is 'Inf' or 'NaN'.

Works for me; that's easier anyway.

The attached v3 patch fixes these things and also takes care of an
oversight in v2: I'd made numeric() apply typmod restrictions to Inf,
but not numeric_in() or numeric_recv().  I believe the patch itself
is in pretty good shape now, though there are still some issues
elsewhere as noted in the first message in this thread.

Thanks for reviewing!

            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 3df189ad85..a9ed269e15 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,39 @@ 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 generally used to
+     represent undefined calculational results.  With very few exceptions,
+     any operation with a <literal>NaN</literal> input yields
+     another <literal>NaN</literal>.
     </para>

     <note>
@@ -781,9 +812,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 +836,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 eea4239854..6c596dc0ef 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -108,14 +108,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
@@ -167,25 +166,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.
  */
@@ -201,7 +222,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
@@ -209,7 +236,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 \
@@ -226,7 +255,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
@@ -273,7 +304,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 */
@@ -353,16 +384,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


@@ -378,6 +419,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};
@@ -415,6 +459,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
@@ -464,10 +514,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);
@@ -493,6 +545,7 @@ static int    cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
                            int var1weight, int var1sign,
                            const NumericDigit *var2digits, int var2ndigits,
                            int var2weight, int var2sign);
+static bool numeric_is_integral(Numeric num);
 static void add_var(const NumericVar *var1, const NumericVar *var2,
                     NumericVar *result);
 static void sub_var(const NumericVar *var1, const NumericVar *var2,
@@ -585,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
     {
@@ -618,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)
         {
@@ -634,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);
 }

@@ -653,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.
@@ -679,6 +775,17 @@ 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_maximum_size() -
  *
@@ -730,10 +837,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);

@@ -758,10 +872,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);

@@ -821,7 +942,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")));
@@ -847,13 +970,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);
+
+        res = make_result(&value);
+    }
+    else
+    {
+        /* apply_typmod_special wants us to make the Numeric first */
+        res = make_result(&value);

-    apply_typmod(&value, typmod);
+        apply_typmod_special(res, typmod);
+    }

-    res = make_result(&value);
     free_var(&value);

     PG_RETURN_NUMERIC(res);
@@ -959,21 +1098,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
@@ -995,8 +1134,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)
@@ -1097,21 +1235,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);

@@ -1125,24 +1262,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))
@@ -1164,12 +1302,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;
 }

 /*
@@ -1182,37 +1350,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;
 }


@@ -1232,10 +1388,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
@@ -1281,10 +1437,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
@@ -1326,8 +1482,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);
@@ -1351,8 +1510,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);
@@ -1388,26 +1550,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);

@@ -1508,12 +1690,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);
@@ -1717,9 +1908,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
     {
@@ -1845,7 +2041,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;
@@ -2148,20 +2344,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
     {
@@ -2188,10 +2406,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")));
@@ -2212,6 +2432,45 @@ in_range_numeric_numeric(PG_FUNCTION_ARGS)
     {
         result = less;            /* non-NAN < NAN */
     }
+
+    /*
+     * Deal with infinite offset (necessarily +Inf, at this point).  Infinite
+     * val and/or base cannot affect the conclusion.
+     */
+    else if (NUMERIC_IS_SPECIAL(offset))
+    {
+        Assert(NUMERIC_IS_PINF(offset));
+        result = (sub ? !less : less);
+    }
+
+    /*
+     * 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
     {
         /*
@@ -2262,8 +2521,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);
@@ -2343,7 +2602,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);
@@ -2427,10 +2687,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.
@@ -2483,10 +2765,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.
@@ -2539,10 +2843,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.
@@ -2603,10 +2961,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
@@ -2659,10 +3074,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
@@ -2721,8 +3183,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);
@@ -2761,10 +3249,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
@@ -2848,9 +3336,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));

     /*
@@ -2890,9 +3379,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));

     /*
@@ -2997,10 +3487,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
@@ -3048,10 +3546,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
@@ -3109,10 +3613,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);
@@ -3151,10 +3662,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
@@ -3180,7 +3720,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)
@@ -3190,60 +3730,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));

-    set_var_from_var(&arg2, &arg2_trunc);
-    trunc_var(&arg2_trunc, 0);
+        /*
+         * 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));
+
+        /*
+         * 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.
@@ -3253,7 +3903,6 @@ numeric_power(PG_FUNCTION_ARGS)
     res = make_result(&result);

     free_var(&result);
-    free_var(&arg2_trunc);

     PG_RETURN_NUMERIC(res);
 }
@@ -3268,7 +3917,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));
@@ -3335,7 +3984,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);
@@ -3355,8 +4004,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);
@@ -3403,7 +4052,7 @@ numeric_int4_opt_error(Numeric num, bool *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)
         {
@@ -3412,9 +4061,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")));
         }
     }

@@ -3494,10 +4148,17 @@ numeric_int8(PG_FUNCTION_ARGS)
     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);
@@ -3539,10 +4200,17 @@ numeric_int2(PG_FUNCTION_ARGS)
     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);
@@ -3577,9 +4245,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);

@@ -3603,8 +4274,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)));
@@ -3628,6 +4306,7 @@ numeric_float8_no_overflow(PG_FUNCTION_ARGS)
     Numeric        num = PG_GETARG_NUMERIC(0);
     double        val;

+    /* numeric_to_double_no_overflow will handle infinities */
     if (NUMERIC_IS_NAN(num))
         PG_RETURN_FLOAT8(get_float8_nan());

@@ -3648,9 +4327,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);

@@ -3674,8 +4356,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)));
@@ -3711,9 +4400,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.
@@ -3765,10 +4460,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;
     }

@@ -3830,10 +4530,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;
     }

@@ -3956,6 +4661,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;

@@ -3969,6 +4676,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)
     {
@@ -4044,6 +4753,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;

@@ -4056,6 +4767,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)
     {
@@ -4134,6 +4847,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);
@@ -4188,6 +4907,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);

@@ -4256,6 +4981,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);
@@ -4319,6 +5050,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);

@@ -5111,12 +5848,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);
@@ -5137,12 +5882,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);
@@ -5178,9 +5931,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;
@@ -5195,9 +5948,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 */
@@ -5840,6 +6594,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;
@@ -5871,6 +6631,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;
@@ -6425,6 +7191,19 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
 }


+/*
+ * duplicate_numeric() - copy a packed-format Numeric
+ */
+static Numeric
+duplicate_numeric(Numeric num)
+{
+    Numeric        res;
+
+    res = (Numeric) palloc(VARSIZE(num));
+    memcpy(res, num, VARSIZE(num));
+    return res;
+}
+
 /*
  * make_result_opt_error() -
  *
@@ -6446,12 +7225,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);
@@ -6542,8 +7331,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)
@@ -6616,6 +7405,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.
  *
@@ -6860,6 +7688,9 @@ int128_to_numericvar(int128 val, NumericVar *var)

 /*
  * Convert numeric to float8; if out of range, return +/- HUGE_VAL
+ *
+ * Note the caller must already have dealt with NaN, but we can
+ * handle infinities here.
  */
 static double
 numeric_to_double_no_overflow(Numeric num)
@@ -6868,6 +7699,15 @@ numeric_to_double_no_overflow(Numeric num)
     double        val;
     char       *endptr;

+    if (NUMERIC_IS_SPECIAL(num))
+    {
+        Assert(!NUMERIC_IS_NAN(num));
+        if (NUMERIC_IS_PINF(num))
+            return HUGE_VAL;
+        else
+            return -HUGE_VAL;
+    }
+
     tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
                                               NumericGetDatum(num)));

@@ -6971,6 +7811,28 @@ cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
                           var1digits, var1ndigits, var1weight);
 }

+/*
+ * Test whether a Numeric value is 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);
+}
+

 /*
  * add_var() -
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 c7fe63d037..ec46f703d8 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 d5fd4045f9..2512852a94 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
@@ -2006,7 +2006,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
@@ -2014,7 +2014,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)

@@ -2024,7 +2024,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
@@ -2032,7 +2032,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)

@@ -2044,6 +2044,24 @@ 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
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 41475a9a24..9091087ba3 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,6 +1287,10 @@ 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 fe273aa31e..b4e7f6d8c6 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
@@ -558,6 +558,10 @@ 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
              1.1 preceding and 'NaN' following);  -- error, NaN disallowed


Re: Infinities in type numeric

От
Dean Rasheed
Дата:
On Tue, 16 Jun 2020 at 18:24, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> The attached v3 patch fixes these things and also takes care of an
> oversight in v2: I'd made numeric() apply typmod restrictions to Inf,
> but not numeric_in() or numeric_recv().  I believe the patch itself
> is in pretty good shape now, though there are still some issues
> elsewhere as noted in the first message in this thread.
>

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.

I tried to go over all the edge cases and I think they all make sense,
except for a couple of cases which I've listed below, along with a few
other minor comments:

1). I don't think that the way in_range() handles infinities is quite
right. For example:

SELECT in_range('inf'::numeric, 10::numeric, 'inf'::numeric, false, false);
 in_range
----------
 f
(1 row)

But I think that should return "val >= base + offset", which is "Inf
>= Inf", which should be true.

Similarly, I think this should return true:

SELECT in_range('-inf'::numeric, 10::numeric, 'inf'::numeric, true, true);
 in_range
----------
 f
(1 row)

I think this could use some test coverage.

2). I think numeric_pg_lsn() needs updating -- this should probably be an error:

SELECT pg_lsn('inf'::numeric);
 pg_lsn
--------
 0/0
(1 row)

3). In the bottom half of numeric.c, there's a section header comment
saying "Local functions follow ... In general, these do not support
NaNs ...". That should probably also mention infinities. There are
also now more functions to mention that are exceptions to that comment
about not handling NaN/Inf, but I think that some of the new
exceptions can be avoided.

4). The comment for set_var_from_str() mentions that it doesn't handle
"NaN", so on the face of it, it ought to also mention that it doesn't
handle "Infinity" either. However, this is only a few lines down from
that "Local functions follow ..." section header comment, which
already covers that, so it seems pointless mentioning NaNs and
infinities again for this function (none of the other local functions
in that section of the file do).

5). It seems a bit odd that numeric_to_double_no_overflow() handles
infinite inputs, but not NaN inputs, while its only caller
numeric_float8_no_overflow() handles NaNs, but not infinities. ISTM
that it would be neater to have all the special-handling in one place
(in the caller). That would also stop numeric_to_double_no_overflow()
being an exception to the preceding section header comment about local
functions not handling Nan/Inf. In fact, I wonder why keep
numeric_to_double_no_overflow() at all? It could just be rolled into
its caller, making it more like numeric_float8().

6). The next function, numericvar_to_double_no_overflow(), has a
comment that just says "As above, but work from a NumericVar", but it
isn't really "as above" anymore, since it doesn't handle infinite
inputs. Depending on what happens to numeric_to_double_no_overflow(),
this function's comment might need some tweaking.

7). The new function numeric_is_integral() feels out of place where it
is, amongst arithmetic functions operating on NumericVar's, because it
operates on a Numeric, and also because it handles NaNs, making it
another exception to the preceding comment about local functions that
don't handle NaNs. Perhaps it would fit in better after
numeric_is_nan() and numeric_is_inf(). Even though it's a local
function, it feels more akin to those functions.

Finally, not really in the scope of this patch, but something I
noticed anyway while looking at edge cases -- float and numeric handle
NaN/0 differently:

SELECT 'nan'::float8 / 0::float8;
ERROR:  division by zero

SELECT 'nan'::numeric / 0::numeric;
 ?column?
----------
      NaN

I'm not sure if this is worth worrying about, or which behaviour is
preferable though.

Regards,
Dean



Re: Infinities in type numeric

От
Tom Lane
Дата:
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!  Yeah, Alexander fixed the jsonpath
stuff at df646509f, so I think all my original concerns are cleared,
other than the question of whether to invent isfinite() and isnan()
SQL functions.  That seems like follow-on work in any case.

> 1). I don't think that the way in_range() handles infinities is quite
> right. For example:

> SELECT in_range('inf'::numeric, 10::numeric, 'inf'::numeric, false, false);
>  in_range
> ----------
>  f
> (1 row)

> But I think that should return "val >= base + offset", which is "Inf
> >= Inf", which should be true.

Hmm.  I modeled the logic on the float8 in_range code, which does the
same thing:

# SELECT in_range('inf'::float8, 10::float8, 'inf'::float8, false, false);
 in_range
----------
 f
(1 row)

It does seem like this is wrong per the specification of in_range, though,
so do we have a bug to fix in the float in_range support?  If so I'd
be inclined to go correct that first and then adapt the numeric patch
to match.

> Similarly, I think this should return true:
> SELECT in_range('-inf'::numeric, 10::numeric, 'inf'::numeric, true, true);

Same comment.

> I think this could use some test coverage.

Evidently :-(

> 2). I think numeric_pg_lsn() needs updating -- this should probably be an error:

Oh, that was not there when I produced my patch.  Will cover it in the
next version.

I agree with your other comments and will update the patch.

> Finally, not really in the scope of this patch, but something I
> noticed anyway while looking at edge cases -- float and numeric handle
> NaN/0 differently:
> SELECT 'nan'::float8 / 0::float8;
> ERROR:  division by zero
> SELECT 'nan'::numeric / 0::numeric;
>  ?column?
> ----------
>       NaN

Hmm.  It seems like we generally ought to try to follow IEEE 754
for the semantics of operations on NaN, but I don't have a copy of
that spec so I'm not sure which result it specifies for this.
I agree that being inconsistent between the two types is not what
we want.

            regards, tom lane



Re: Infinities in type numeric

От
Tom Lane
Дата:
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


Re: Infinities in type numeric

От
Dean Rasheed
Дата:
On Tue, 21 Jul 2020 at 23:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Here's a v4 that syncs numeric in_range() with the new behavior of
> float in_range(), and addresses your other comments too.
>

LGTM.

Regards,
Dean



Re: Infinities in type numeric

От
Tom Lane
Дата:
Dean Rasheed <dean.a.rasheed@gmail.com> writes:
> On Tue, 21 Jul 2020 at 23:18, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Here's a v4 that syncs numeric in_range() with the new behavior of
>> float in_range(), and addresses your other comments too.

> LGTM.

Pushed.  Thanks again for reviewing!

            regards, tom lane