]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/numeric.c
Remove all the special-case code for INT64_IS_BUSTED, per decision that
[postgresql] / src / backend / utils / adt / numeric.c
index f173f30211c7a62d414e2d4f18bc2165223d7e44..2892b5d2fbf0759be09ee6b21daa5ce952c5836c 100644 (file)
  * Transactions on Mathematical Software, Vol. 24, No. 4, December 1998,
  * pages 359-367.
  *
- * Copyright (c) 1998-2007, PostgreSQL Global Development Group
+ * Copyright (c) 1998-2010, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.101 2007/02/27 23:48:08 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.121 2010/01/07 04:53:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include <limits.h>
 #include <math.h>
 
+#include "access/hash.h"
 #include "catalog/pg_type.h"
 #include "libpq/pqformat.h"
+#include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/int8.h"
@@ -51,7 +53,7 @@
  * NBASE that's less than sqrt(INT_MAX), in practice we are only interested
  * in NBASE a power of ten, so that I/O conversions and decimal rounding
  * are easy.  Also, it's actually more efficient if NBASE is rather less than
- * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var to
+ * sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var_fast to
  * postpone processing carries.
  * ----------
  */
@@ -88,6 +90,10 @@ typedef int16 NumericDigit;
 
 
 /* ----------
+ * NumericVar is the format we use for arithmetic.     The digit-array part
+ * is the same as the NumericData storage format, but the header is more
+ * complex.
+ *
  * The value represented by a NumericVar is determined by the sign, weight,
  * ndigits, and digits[] array.
  * Note: the first digit of a NumericVar's value is assumed to be multiplied
@@ -98,7 +104,7 @@ typedef int16 NumericDigit;
  * NumericVar. digits points at the first digit in actual use (the one
  * with the specified weight). We normally leave an unused digit or two
  * (preset to zeroes) between buf and digits, so that there is room to store
- * a carry out of the top digit without special pushups.  We just need to
+ * a carry out of the top digit without reallocating space.  We just need to
  * decrement digits (and increment weight) to make room for the carry digit.
  * (There is no such extra space in a numeric value stored in the database,
  * only in a NumericVar in memory.)
@@ -236,10 +242,12 @@ static void alloc_var(NumericVar *var, int ndigits);
 static void free_var(NumericVar *var);
 static void zero_var(NumericVar *var);
 
-static void set_var_from_str(const char *str, NumericVar *dest);
+static const char *set_var_from_str(const char *str, const char *cp,
+                                NumericVar *dest);
 static void set_var_from_num(Numeric value, NumericVar *dest);
 static void set_var_from_var(NumericVar *value, NumericVar *dest);
 static char *get_str_from_var(NumericVar *var, int dscale);
+static char *get_str_from_var_sci(NumericVar *var, int rscale);
 
 static Numeric make_result(NumericVar *var);
 
@@ -263,6 +271,8 @@ static void mul_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
                int rscale);
 static void div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
                int rscale, bool round);
+static void div_var_fast(NumericVar *var1, NumericVar *var2, NumericVar *result,
+                        int rscale, bool round);
 static int     select_div_scale(NumericVar *var1, NumericVar *var2);
 static void mod_var(NumericVar *var1, NumericVar *var2, NumericVar *result);
 static void ceil_var(NumericVar *var, NumericVar *result);
@@ -313,26 +323,69 @@ numeric_in(PG_FUNCTION_ARGS)
        Oid                     typelem = PG_GETARG_OID(1);
 #endif
        int32           typmod = PG_GETARG_INT32(2);
-       NumericVar      value;
        Numeric         res;
+       const char *cp;
+
+       /* Skip leading spaces */
+       cp = str;
+       while (*cp)
+       {
+               if (!isspace((unsigned char) *cp))
+                       break;
+               cp++;
+       }
 
        /*
         * Check for NaN
         */
-       if (pg_strcasecmp(str, "NaN") == 0)
-               PG_RETURN_NUMERIC(make_result(&const_nan));
+       if (pg_strncasecmp(cp, "NaN", 3) == 0)
+       {
+               res = make_result(&const_nan);
 
-       /*
-        * Use set_var_from_str() to parse the input string and return it in the
-        * packed DB storage format
-        */
-       init_var(&value);
-       set_var_from_str(str, &value);
+               /* 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 numeric: \"%s\"",
+                                                        str)));
+                       cp++;
+               }
+       }
+       else
+       {
+               /*
+                * Use set_var_from_str() to parse a normal numeric value
+                */
+               NumericVar      value;
 
-       apply_typmod(&value, typmod);
+               init_var(&value);
 
-       res = make_result(&value);
-       free_var(&value);
+               cp = set_var_from_str(str, cp, &value);
+
+               /*
+                * 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.
+                */
+               while (*cp)
+               {
+                       if (!isspace((unsigned char) *cp))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+                                         errmsg("invalid input syntax for type numeric: \"%s\"",
+                                                        str)));
+                       cp++;
+               }
+
+               apply_typmod(&value, typmod);
+
+               res = make_result(&value);
+               free_var(&value);
+       }
 
        PG_RETURN_NUMERIC(res);
 }
@@ -374,6 +427,32 @@ numeric_out(PG_FUNCTION_ARGS)
        PG_RETURN_CSTRING(str);
 }
 
+/*
+ * numeric_out_sci() -
+ *
+ *     Output function for numeric data type in scientific notation.
+ */
+char *
+numeric_out_sci(Numeric num, int scale)
+{
+       NumericVar      x;
+       char       *str;
+
+       /*
+        * Handle NaN
+        */
+       if (NUMERIC_IS_NAN(num))
+               return pstrdup("NaN");
+
+       init_var(&x);
+       set_var_from_num(num, &x);
+
+       str = get_str_from_var_sci(&x, scale);
+
+       free_var(&x);
+       return str;
+}
+
 /*
  *             numeric_recv                    - converts external binary format to numeric
  *
@@ -470,7 +549,7 @@ numeric_send(PG_FUNCTION_ARGS)
  *     scale of the attribute have to be applied on the value.
  */
 Datum
-numeric(PG_FUNCTION_ARGS)
+numeric                (PG_FUNCTION_ARGS)
 {
        Numeric         num = PG_GETARG_NUMERIC(0);
        int32           typmod = PG_GETARG_INT32(1);
@@ -540,12 +619,12 @@ numeric(PG_FUNCTION_ARGS)
 Datum
 numerictypmodin(PG_FUNCTION_ARGS)
 {
-       ArrayType   *ta = PG_GETARG_ARRAYTYPE_P(0);
-       int32           *tl;
+       ArrayType  *ta = PG_GETARG_ARRAYTYPE_P(0);
+       int32      *tl;
        int                     n;
        int32           typmod;
 
-       tl = ArrayGetTypmods(ta, &n);
+       tl = ArrayGetIntegerTypmods(ta, &n);
 
        if (n == 2)
        {
@@ -557,8 +636,8 @@ numerictypmodin(PG_FUNCTION_ARGS)
                if (tl[1] < 0 || tl[1] > tl[0])
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                        errmsg("NUMERIC scale %d must be between 0 and precision %d",
-                                                       tl[1], tl[0])));
+                               errmsg("NUMERIC scale %d must be between 0 and precision %d",
+                                          tl[1], tl[0])));
                typmod = ((tl[0] << 16) | tl[1]) + VARHDRSZ;
        }
        else if (n == 1)
@@ -575,7 +654,7 @@ numerictypmodin(PG_FUNCTION_ARGS)
        {
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                                               errmsg("invalid NUMERIC type modifier")));
+                                errmsg("invalid NUMERIC type modifier")));
                typmod = 0;                             /* keep compiler quiet */
        }
 
@@ -585,8 +664,8 @@ numerictypmodin(PG_FUNCTION_ARGS)
 Datum
 numerictypmodout(PG_FUNCTION_ARGS)
 {
-       int32   typmod = PG_GETARG_INT32(0);
-       char   *res = (char *) palloc(64);
+       int32           typmod = PG_GETARG_INT32(0);
+       char       *res = (char *) palloc(64);
 
        if (typmod >= 0)
                snprintf(res, 64, "(%d,%d)",
@@ -907,7 +986,7 @@ width_bucket_numeric(PG_FUNCTION_ARGS)
                NUMERIC_IS_NAN(bound2))
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
-                                errmsg("operand, lower bound and upper bound cannot be NaN")));
+                         errmsg("operand, lower bound and upper bound cannot be NaN")));
 
        init_var(&result_var);
        init_var(&count_var);
@@ -1149,6 +1228,80 @@ cmp_numerics(Numeric num1, Numeric num2)
        return result;
 }
 
+Datum
+hash_numeric(PG_FUNCTION_ARGS)
+{
+       Numeric         key = PG_GETARG_NUMERIC(0);
+       Datum           digit_hash;
+       Datum           result;
+       int                     weight;
+       int                     start_offset;
+       int                     end_offset;
+       int                     i;
+       int                     hash_len;
+
+       /* If it's NaN, don't try to hash the rest of the fields */
+       if (NUMERIC_IS_NAN(key))
+               PG_RETURN_UINT32(0);
+
+       weight = key->n_weight;
+       start_offset = 0;
+       end_offset = 0;
+
+       /*
+        * Omit any leading or trailing zeros from the input to the hash. The
+        * numeric implementation *should* guarantee that leading and trailing
+        * zeros are suppressed, but we're paranoid. Note that we measure the
+        * starting and ending offsets in units of NumericDigits, not bytes.
+        */
+       for (i = 0; i < NUMERIC_NDIGITS(key); i++)
+       {
+               if (NUMERIC_DIGITS(key)[i] != (NumericDigit) 0)
+                       break;
+
+               start_offset++;
+
+               /*
+                * The weight is effectively the # of digits before the decimal point,
+                * so decrement it for each leading zero we skip.
+                */
+               weight--;
+       }
+
+       /*
+        * If there are no non-zero digits, then the value of the number is zero,
+        * regardless of any other fields.
+        */
+       if (NUMERIC_NDIGITS(key) == start_offset)
+               PG_RETURN_UINT32(-1);
+
+       for (i = NUMERIC_NDIGITS(key) - 1; i >= 0; i--)
+       {
+               if (NUMERIC_DIGITS(key)[i] != (NumericDigit) 0)
+                       break;
+
+               end_offset++;
+       }
+
+       /* If we get here, there should be at least one non-zero digit */
+       Assert(start_offset + end_offset < NUMERIC_NDIGITS(key));
+
+       /*
+        * Note that we don't hash on the Numeric's scale, since two numerics can
+        * compare equal but have different scales. We also don't hash on the
+        * sign, although we could: since a sign difference implies inequality,
+        * this shouldn't affect correctness.
+        */
+       hash_len = NUMERIC_NDIGITS(key) - start_offset - end_offset;
+       digit_hash = hash_any((unsigned char *) (NUMERIC_DIGITS(key) + start_offset),
+                                                 hash_len * sizeof(NumericDigit));
+
+       /* Mix in the weight, via XOR */
+       result = digit_hash ^ weight;
+
+       PG_RETURN_DATUM(result);
+}
+
 
 /* ----------------------------------------------------------------------
  *
@@ -1343,6 +1496,52 @@ numeric_div(PG_FUNCTION_ARGS)
 }
 
 
+/*
+ * numeric_div_trunc() -
+ *
+ *     Divide one numeric into another, truncating the result to an integer
+ */
+Datum
+numeric_div_trunc(PG_FUNCTION_ARGS)
+{
+       Numeric         num1 = PG_GETARG_NUMERIC(0);
+       Numeric         num2 = PG_GETARG_NUMERIC(1);
+       NumericVar      arg1;
+       NumericVar      arg2;
+       NumericVar      result;
+       Numeric         res;
+
+       /*
+        * Handle NaN
+        */
+       if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+               PG_RETURN_NUMERIC(make_result(&const_nan));
+
+       /*
+        * Unpack the arguments
+        */
+       init_var(&arg1);
+       init_var(&arg2);
+       init_var(&result);
+
+       set_var_from_num(num1, &arg1);
+       set_var_from_num(num2, &arg2);
+
+       /*
+        * Do the divide and return the result
+        */
+       div_var(&arg1, &arg2, &result, 0, false);
+
+       res = make_result(&result);
+
+       free_var(&arg1);
+       free_var(&arg2);
+       free_var(&result);
+
+       PG_RETURN_NUMERIC(res);
+}
+
+
 /*
  * numeric_mod() -
  *
@@ -1484,6 +1683,11 @@ numeric_fac(PG_FUNCTION_ARGS)
                res = make_result(&const_one);
                PG_RETURN_NUMERIC(res);
        }
+       /* Fail immediately if the result would overflow */
+       if (num > 32177)
+               ereport(ERROR,
+                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                errmsg("value overflows numeric format")));
 
        init_var(&fact);
        init_var(&result);
@@ -1492,6 +1696,9 @@ numeric_fac(PG_FUNCTION_ARGS)
 
        for (num = num - 1; num > 1; num--)
        {
+               /* this loop can take awhile, so allow it to be interrupted */
+               CHECK_FOR_INTERRUPTS();
+
                int8_to_numericvar(num, &fact);
 
                mul_var(&result, &fact, &result, 0);
@@ -1757,16 +1964,21 @@ numeric_power(PG_FUNCTION_ARGS)
        trunc_var(&arg2_trunc, 0);
 
        /*
-        * Return special SQLSTATE error codes for a few conditions mandated by
-        * the standard.
+        * 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) ||
-               (cmp_var(&arg1, &const_zero) < 0 &&
-                cmp_var(&arg2, &arg2_trunc) != 0))
+       if (cmp_var(&arg1, &const_zero) == 0 &&
+               cmp_var(&arg2, &const_zero) < 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)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
-                                errmsg("invalid argument for power function")));
+                                errmsg("a negative number raised to a non-integer power yields a complex result")));
 
        /*
         * Call power_var() to compute and return the result; note it handles
@@ -1980,7 +2192,9 @@ float8_numeric(PG_FUNCTION_ARGS)
 
        init_var(&result);
 
-       set_var_from_str(buf, &result);
+       /* Assume we need not worry about leading/trailing spaces */
+       (void) set_var_from_str(buf, buf, &result);
+
        res = make_result(&result);
 
        free_var(&result);
@@ -2040,7 +2254,9 @@ float4_numeric(PG_FUNCTION_ARGS)
 
        init_var(&result);
 
-       set_var_from_str(buf, &result);
+       /* Assume we need not worry about leading/trailing spaces */
+       (void) set_var_from_str(buf, buf, &result);
+
        res = make_result(&result);
 
        free_var(&result);
@@ -2070,50 +2286,6 @@ numeric_float4(PG_FUNCTION_ARGS)
 }
 
 
-Datum
-text_numeric(PG_FUNCTION_ARGS)
-{
-       text       *str = PG_GETARG_TEXT_P(0);
-       int                     len;
-       char       *s;
-       Datum           result;
-
-       len = (VARSIZE(str) - VARHDRSZ);
-       s = palloc(len + 1);
-       memcpy(s, VARDATA(str), len);
-       *(s + len) = '\0';
-
-       result = DirectFunctionCall3(numeric_in, CStringGetDatum(s),
-                                                                ObjectIdGetDatum(0), Int32GetDatum(-1));
-
-       pfree(s);
-
-       return result;
-}
-
-Datum
-numeric_text(PG_FUNCTION_ARGS)
-{
-       /* val is numeric, but easier to leave it as Datum */
-       Datum           val = PG_GETARG_DATUM(0);
-       char       *s;
-       int                     len;
-       text       *result;
-
-       s = DatumGetCString(DirectFunctionCall1(numeric_out, val));
-       len = strlen(s);
-
-       result = (text *) palloc(VARHDRSZ + len);
-
-       SET_VARSIZE(result, VARHDRSZ + len);
-       memcpy(VARDATA(result), s, len);
-
-       pfree(s);
-
-       PG_RETURN_TEXT_P(result);
-}
-
-
 /* ----------------------------------------------------------------------
  *
  * Aggregate functions
@@ -2394,7 +2566,10 @@ numeric_stddev_internal(ArrayType *transarray,
        }
        else
        {
-               mul_var(&vN, &vNminus1, &vNminus1, 0);  /* N * (N - 1) */
+               if (sample)
+                       mul_var(&vN, &vNminus1, &vNminus1, 0);          /* N * (N - 1) */
+               else
+                       mul_var(&vN, &vN, &vNminus1, 0);        /* N * N */
                rscale = select_div_scale(&vsumX2, &vNminus1);
                div_var(&vsumX2, &vNminus1, &vsumX, rscale, true);              /* variance */
                if (!variance)
@@ -2504,11 +2679,16 @@ int2_sum(PG_FUNCTION_ARGS)
        }
 
        /*
-        * If we're invoked by nodeAgg, we can cheat and modify out first
+        * If we're invoked by nodeAgg, we can cheat and modify our first
         * parameter in-place to avoid palloc overhead. If not, we need to return
-        * the new value of the transition variable.
+        * the new value of the transition variable. (If int8 is pass-by-value,
+        * then of course this is useless as well as incorrect, so just ifdef it
+        * out.)
         */
-       if (fcinfo->context && IsA(fcinfo->context, AggState))
+#ifndef USE_FLOAT8_BYVAL               /* controls int8 too */
+       if (fcinfo->context &&
+               (IsA(fcinfo->context, AggState) ||
+                IsA(fcinfo->context, WindowAggState)))
        {
                int64      *oldsum = (int64 *) PG_GETARG_POINTER(0);
 
@@ -2519,6 +2699,7 @@ int2_sum(PG_FUNCTION_ARGS)
                PG_RETURN_POINTER(oldsum);
        }
        else
+#endif
        {
                int64           oldsum = PG_GETARG_INT64(0);
 
@@ -2549,11 +2730,16 @@ int4_sum(PG_FUNCTION_ARGS)
        }
 
        /*
-        * If we're invoked by nodeAgg, we can cheat and modify out first
+        * If we're invoked by nodeAgg, we can cheat and modify our first
         * parameter in-place to avoid palloc overhead. If not, we need to return
-        * the new value of the transition variable.
+        * the new value of the transition variable. (If int8 is pass-by-value,
+        * then of course this is useless as well as incorrect, so just ifdef it
+        * out.)
         */
-       if (fcinfo->context && IsA(fcinfo->context, AggState))
+#ifndef USE_FLOAT8_BYVAL               /* controls int8 too */
+       if (fcinfo->context &&
+               (IsA(fcinfo->context, AggState) ||
+                IsA(fcinfo->context, WindowAggState)))
        {
                int64      *oldsum = (int64 *) PG_GETARG_POINTER(0);
 
@@ -2564,6 +2750,7 @@ int4_sum(PG_FUNCTION_ARGS)
                PG_RETURN_POINTER(oldsum);
        }
        else
+#endif
        {
                int64           oldsum = PG_GETARG_INT64(0);
 
@@ -2621,16 +2808,8 @@ int8_sum(PG_FUNCTION_ARGS)
 
 typedef struct Int8TransTypeData
 {
-#ifndef INT64_IS_BUSTED
        int64           count;
        int64           sum;
-#else
-       /* "int64" isn't really 64 bits, so fake up properly-aligned fields */
-       int32           count;
-       int32           pad1;
-       int32           sum;
-       int32           pad2;
-#endif
 } Int8TransTypeData;
 
 Datum
@@ -2645,7 +2824,9 @@ int2_avg_accum(PG_FUNCTION_ARGS)
         * parameter in-place to reduce palloc overhead. Otherwise we need to make
         * a copy of it before scribbling on it.
         */
-       if (fcinfo->context && IsA(fcinfo->context, AggState))
+       if (fcinfo->context &&
+               (IsA(fcinfo->context, AggState) ||
+                IsA(fcinfo->context, WindowAggState)))
                transarray = PG_GETARG_ARRAYTYPE_P(0);
        else
                transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
@@ -2673,7 +2854,9 @@ int4_avg_accum(PG_FUNCTION_ARGS)
         * parameter in-place to reduce palloc overhead. Otherwise we need to make
         * a copy of it before scribbling on it.
         */
-       if (fcinfo->context && IsA(fcinfo->context, AggState))
+       if (fcinfo->context &&
+               (IsA(fcinfo->context, AggState) ||
+                IsA(fcinfo->context, WindowAggState)))
                transarray = PG_GETARG_ARRAYTYPE_P(0);
        else
                transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
@@ -2856,11 +3039,17 @@ zero_var(NumericVar *var)
  * set_var_from_str()
  *
  *     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.
+ *
+ * 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.)
  */
-static void
-set_var_from_str(const char *str, NumericVar *dest)
+static const char *
+set_var_from_str(const char *str, const char *cp, NumericVar *dest)
 {
-       const char *cp = str;
        bool            have_dp = FALSE;
        int                     i;
        unsigned char *decdigits;
@@ -2877,15 +3066,6 @@ set_var_from_str(const char *str, NumericVar *dest)
         * We first parse the string to extract decimal digits and determine the
         * correct decimal weight.      Then convert to NBASE representation.
         */
-
-       /* skip leading spaces */
-       while (*cp)
-       {
-               if (!isspace((unsigned char) *cp))
-                       break;
-               cp++;
-       }
-
        switch (*cp)
        {
                case '+':
@@ -2970,17 +3150,6 @@ set_var_from_str(const char *str, NumericVar *dest)
                        dscale = 0;
        }
 
-       /* 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 numeric: \"%s\"",
-                                                       str)));
-               cp++;
-       }
-
        /*
         * Okay, convert pure-decimal representation to base NBASE.  First we need
         * to determine the converted weight and ndigits.  offset is the number of
@@ -3021,6 +3190,9 @@ set_var_from_str(const char *str, NumericVar *dest)
 
        /* Strip any leading/trailing zeroes, and normalize weight if zero */
        strip_var(dest);
+
+       /* Return end+1 position for caller */
+       return cp;
 }
 
 
@@ -3211,6 +3383,110 @@ get_str_from_var(NumericVar *var, int dscale)
        return str;
 }
 
+/*
+ * get_str_from_var_sci() -
+ *
+ *     Convert a var to a normalised scientific notation text representation.
+ *     This function does the heavy lifting for numeric_out_sci().
+ *
+ *     This notation has the general form a * 10^b, where a is known as the
+ *     "significand" and b is known as the "exponent".
+ *
+ *     Because we can't do superscript in ASCII (and because we want to copy
+ *     printf's behaviour) we display the exponent using E notation, with a
+ *     minimum of two exponent digits.
+ *
+ *     For example, the value 1234 could be output as 1.2e+03.
+ *
+ *     We assume that the exponent can fit into an int32.
+ *
+ *     rscale is the number of decimal digits desired after the decimal point in
+ *     the output, negative values will be treated as meaning zero.
+ *
+ *     CAUTION: var's contents may be modified by rounding!
+ *
+ *     Returns a palloc'd string.
+ */
+static char *
+get_str_from_var_sci(NumericVar *var, int rscale)
+{
+       int32           exponent;
+       NumericVar  denominator;
+       NumericVar      significand;
+       int                     denom_scale;
+       size_t          len;
+       char       *str;
+       char       *sig_out;
+
+       if (rscale < 0)
+               rscale = 0;
+
+       /*
+        * Determine the exponent of this number in normalised form.
+        *
+        * This is the exponent required to represent the number with only one
+        * significant digit before the decimal place.
+        */
+       if (var->ndigits > 0)
+       {
+               exponent = (var->weight + 1) * DEC_DIGITS;
+
+               /*
+                * Compensate for leading decimal zeroes in the first numeric digit by
+                * decrementing the exponent.
+                */
+               exponent -= DEC_DIGITS - (int) log10(var->digits[0]);
+       }
+       else
+       {
+               /*
+                * If var has no digits, then it must be zero.
+                *
+                * Zero doesn't technically have a meaningful exponent in normalised
+                * notation, but we just display the exponent as zero for consistency
+                * of output.
+                */
+               exponent = 0;
+       }
+
+       /*
+        * The denominator is set to 10 raised to the power of the exponent.
+        *
+        * We then divide var by the denominator to get the significand, rounding
+        * to rscale decimal digits in the process.
+        */
+       if (exponent < 0)
+               denom_scale = -exponent;
+       else
+               denom_scale = 0;
+
+       init_var(&denominator);
+       init_var(&significand);
+
+       int8_to_numericvar((int64) 10, &denominator);
+       power_var_int(&denominator, exponent, &denominator, denom_scale);
+       div_var(var, &denominator, &significand, rscale, true);
+       sig_out = get_str_from_var(&significand, rscale);
+
+       free_var(&denominator);
+       free_var(&significand);
+
+       /*
+        * Allocate space for the result.
+        *
+        * In addition to the significand, we need room for the exponent decoration
+        * ("e"), the sign of the exponent, up to 10 digits for the exponent
+        * itself, and of course the null terminator.
+        */
+       len = strlen(sig_out) + 13;
+       str = palloc(len);
+       snprintf(str, len, "%se%+03d", sig_out, exponent);
+
+       pfree(sig_out);
+
+       return str;
+}
+
 
 /*
  * make_result() -
@@ -3993,12 +4269,293 @@ mul_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
 /*
  * div_var() -
  *
- *     Division on variable level. Quotient of var1 / var2 is stored
- *     in result.      Result is rounded to no more than rscale fractional digits.
+ *     Division on variable level. Quotient of var1 / var2 is stored in result.
+ *     The quotient is figured to exactly rscale fractional digits.
+ *     If round is true, it is rounded at the rscale'th digit; if false, it
+ *     is truncated (towards zero) at that digit.
  */
 static void
 div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
                int rscale, bool round)
+{
+       int                     div_ndigits;
+       int                     res_ndigits;
+       int                     res_sign;
+       int                     res_weight;
+       int                     carry;
+       int                     borrow;
+       int                     divisor1;
+       int                     divisor2;
+       NumericDigit *dividend;
+       NumericDigit *divisor;
+       NumericDigit *res_digits;
+       int                     i;
+       int                     j;
+
+       /* copy these values into local vars for speed in inner loop */
+       int                     var1ndigits = var1->ndigits;
+       int                     var2ndigits = var2->ndigits;
+
+       /*
+        * First of all division by zero check; we must not be handed an
+        * unnormalized divisor.
+        */
+       if (var2ndigits == 0 || var2->digits[0] == 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DIVISION_BY_ZERO),
+                                errmsg("division by zero")));
+
+       /*
+        * Now result zero check
+        */
+       if (var1ndigits == 0)
+       {
+               zero_var(result);
+               result->dscale = rscale;
+               return;
+       }
+
+       /*
+        * Determine the result sign, weight and number of digits to calculate.
+        * The weight figured here is correct if the emitted quotient has no
+        * leading zero digits; otherwise strip_var() will fix things up.
+        */
+       if (var1->sign == var2->sign)
+               res_sign = NUMERIC_POS;
+       else
+               res_sign = NUMERIC_NEG;
+       res_weight = var1->weight - var2->weight;
+       /* The number of accurate result digits we need to produce: */
+       res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS;
+       /* ... but always at least 1 */
+       res_ndigits = Max(res_ndigits, 1);
+       /* If rounding needed, figure one more digit to ensure correct result */
+       if (round)
+               res_ndigits++;
+
+       /*
+        * The working dividend normally requires res_ndigits + var2ndigits
+        * digits, but make it at least var1ndigits so we can load all of var1
+        * into it.  (There will be an additional digit dividend[0] in the
+        * dividend space, but for consistency with Knuth's notation we don't
+        * count that in div_ndigits.)
+        */
+       div_ndigits = res_ndigits + var2ndigits;
+       div_ndigits = Max(div_ndigits, var1ndigits);
+
+       /*
+        * We need a workspace with room for the working dividend (div_ndigits+1
+        * digits) plus room for the possibly-normalized divisor (var2ndigits
+        * digits).  It is convenient also to have a zero at divisor[0] with the
+        * actual divisor data in divisor[1 .. var2ndigits].  Transferring the
+        * digits into the workspace also allows us to realloc the result (which
+        * might be the same as either input var) before we begin the main loop.
+        * Note that we use palloc0 to ensure that divisor[0], dividend[0], and
+        * any additional dividend positions beyond var1ndigits, start out 0.
+        */
+       dividend = (NumericDigit *)
+               palloc0((div_ndigits + var2ndigits + 2) * sizeof(NumericDigit));
+       divisor = dividend + (div_ndigits + 1);
+       memcpy(dividend + 1, var1->digits, var1ndigits * sizeof(NumericDigit));
+       memcpy(divisor + 1, var2->digits, var2ndigits * sizeof(NumericDigit));
+
+       /*
+        * Now we can realloc the result to hold the generated quotient digits.
+        */
+       alloc_var(result, res_ndigits);
+       res_digits = result->digits;
+
+       if (var2ndigits == 1)
+       {
+               /*
+                * If there's only a single divisor digit, we can use a fast path (cf.
+                * Knuth section 4.3.1 exercise 16).
+                */
+               divisor1 = divisor[1];
+               carry = 0;
+               for (i = 0; i < res_ndigits; i++)
+               {
+                       carry = carry * NBASE + dividend[i + 1];
+                       res_digits[i] = carry / divisor1;
+                       carry = carry % divisor1;
+               }
+       }
+       else
+       {
+               /*
+                * The full multiple-place algorithm is taken from Knuth volume 2,
+                * Algorithm 4.3.1D.
+                *
+                * We need the first divisor digit to be >= NBASE/2.  If it isn't,
+                * make it so by scaling up both the divisor and dividend by the
+                * factor "d".  (The reason for allocating dividend[0] above is to
+                * leave room for possible carry here.)
+                */
+               if (divisor[1] < HALF_NBASE)
+               {
+                       int                     d = NBASE / (divisor[1] + 1);
+
+                       carry = 0;
+                       for (i = var2ndigits; i > 0; i--)
+                       {
+                               carry += divisor[i] * d;
+                               divisor[i] = carry % NBASE;
+                               carry = carry / NBASE;
+                       }
+                       Assert(carry == 0);
+                       carry = 0;
+                       /* at this point only var1ndigits of dividend can be nonzero */
+                       for (i = var1ndigits; i >= 0; i--)
+                       {
+                               carry += dividend[i] * d;
+                               dividend[i] = carry % NBASE;
+                               carry = carry / NBASE;
+                       }
+                       Assert(carry == 0);
+                       Assert(divisor[1] >= HALF_NBASE);
+               }
+               /* First 2 divisor digits are used repeatedly in main loop */
+               divisor1 = divisor[1];
+               divisor2 = divisor[2];
+
+               /*
+                * Begin the main loop.  Each iteration of this loop produces the j'th
+                * quotient digit by dividing dividend[j .. j + var2ndigits] by the
+                * divisor; this is essentially the same as the common manual
+                * procedure for long division.
+                */
+               for (j = 0; j < res_ndigits; j++)
+               {
+                       /* Estimate quotient digit from the first two dividend digits */
+                       int                     next2digits = dividend[j] * NBASE + dividend[j + 1];
+                       int                     qhat;
+
+                       /*
+                        * If next2digits are 0, then quotient digit must be 0 and there's
+                        * no need to adjust the working dividend.      It's worth testing
+                        * here to fall out ASAP when processing trailing zeroes in a
+                        * dividend.
+                        */
+                       if (next2digits == 0)
+                       {
+                               res_digits[j] = 0;
+                               continue;
+                       }
+
+                       if (dividend[j] == divisor1)
+                               qhat = NBASE - 1;
+                       else
+                               qhat = next2digits / divisor1;
+
+                       /*
+                        * Adjust quotient digit if it's too large.  Knuth proves that
+                        * after this step, the quotient digit will be either correct or
+                        * just one too large.  (Note: it's OK to use dividend[j+2] here
+                        * because we know the divisor length is at least 2.)
+                        */
+                       while (divisor2 * qhat >
+                                  (next2digits - qhat * divisor1) * NBASE + dividend[j + 2])
+                               qhat--;
+
+                       /* As above, need do nothing more when quotient digit is 0 */
+                       if (qhat > 0)
+                       {
+                               /*
+                                * Multiply the divisor by qhat, and subtract that from the
+                                * working dividend.  "carry" tracks the multiplication,
+                                * "borrow" the subtraction (could we fold these together?)
+                                */
+                               carry = 0;
+                               borrow = 0;
+                               for (i = var2ndigits; i >= 0; i--)
+                               {
+                                       carry += divisor[i] * qhat;
+                                       borrow -= carry % NBASE;
+                                       carry = carry / NBASE;
+                                       borrow += dividend[j + i];
+                                       if (borrow < 0)
+                                       {
+                                               dividend[j + i] = borrow + NBASE;
+                                               borrow = -1;
+                                       }
+                                       else
+                                       {
+                                               dividend[j + i] = borrow;
+                                               borrow = 0;
+                                       }
+                               }
+                               Assert(carry == 0);
+
+                               /*
+                                * If we got a borrow out of the top dividend digit, then
+                                * indeed qhat was one too large.  Fix it, and add back the
+                                * divisor to correct the working dividend.  (Knuth proves
+                                * that this will occur only about 3/NBASE of the time; hence,
+                                * it's a good idea to test this code with small NBASE to be
+                                * sure this section gets exercised.)
+                                */
+                               if (borrow)
+                               {
+                                       qhat--;
+                                       carry = 0;
+                                       for (i = var2ndigits; i >= 0; i--)
+                                       {
+                                               carry += dividend[j + i] + divisor[i];
+                                               if (carry >= NBASE)
+                                               {
+                                                       dividend[j + i] = carry - NBASE;
+                                                       carry = 1;
+                                               }
+                                               else
+                                               {
+                                                       dividend[j + i] = carry;
+                                                       carry = 0;
+                                               }
+                                       }
+                                       /* A carry should occur here to cancel the borrow above */
+                                       Assert(carry == 1);
+                               }
+                       }
+
+                       /* And we're done with this quotient digit */
+                       res_digits[j] = qhat;
+               }
+       }
+
+       pfree(dividend);
+
+       /*
+        * Finally, round or truncate the result to the requested precision.
+        */
+       result->weight = res_weight;
+       result->sign = res_sign;
+
+       /* Round or truncate to target rscale (and set result->dscale) */
+       if (round)
+               round_var(result, rscale);
+       else
+               trunc_var(result, rscale);
+
+       /* Strip leading and trailing zeroes */
+       strip_var(result);
+}
+
+
+/*
+ * div_var_fast() -
+ *
+ *     This has the same API as div_var, but is implemented using the division
+ *     algorithm from the "FM" library, rather than Knuth's schoolbook-division
+ *     approach.  This is significantly faster but can produce inaccurate
+ *     results, because it sometimes has to propagate rounding to the left,
+ *     and so we can never be entirely sure that we know the requested digits
+ *     exactly.  We compute DIV_GUARD_DIGITS extra digits, but there is
+ *     no certainty that that's enough.  We use this only in the transcendental
+ *     function calculation routines, where everything is approximate anyway.
+ */
+static void
+div_var_fast(NumericVar *var1, NumericVar *var2, NumericVar *result,
+                        int rscale, bool round)
 {
        int                     div_ndigits;
        int                     res_sign;
@@ -4324,30 +4881,21 @@ static void
 mod_var(NumericVar *var1, NumericVar *var2, NumericVar *result)
 {
        NumericVar      tmp;
-       int                     rscale;
 
        init_var(&tmp);
 
        /* ---------
         * We do this using the equation
         *              mod(x,y) = x - trunc(x/y)*y
-        * We set rscale the same way numeric_div and numeric_mul do
-        * to get the right answer from the equation.  The final result,
-        * however, need not be displayed to more precision than the inputs.
+        * div_var can be persuaded to give us trunc(x/y) directly.
         * ----------
         */
-       rscale = select_div_scale(var1, var2);
-
-       div_var(var1, var2, &tmp, rscale, false);
+       div_var(var1, var2, &tmp, 0, false);
 
-       trunc_var(&tmp, 0);
-
-       mul_var(var2, &tmp, &tmp, var2->dscale + tmp.dscale);
+       mul_var(var2, &tmp, &tmp, var2->dscale);
 
        sub_var(var1, &tmp, result);
 
-       round_var(result, Max(var1->dscale, var2->dscale));
-
        free_var(&tmp);
 }
 
@@ -4454,7 +5002,7 @@ sqrt_var(NumericVar *arg, NumericVar *result, int rscale)
 
        for (;;)
        {
-               div_var(&tmp_arg, result, &tmp_val, local_rscale, true);
+               div_var_fast(&tmp_arg, result, &tmp_val, local_rscale, true);
 
                add_var(result, &tmp_val, result);
                mul_var(result, &const_zero_point_five, result, local_rscale);
@@ -4544,7 +5092,7 @@ exp_var(NumericVar *arg, NumericVar *result, int rscale)
 
        /* Compensate for input sign, and round to requested rscale */
        if (xneg)
-               div_var(&const_one, result, result, rscale, true);
+               div_var_fast(&const_one, result, result, rscale, true);
        else
                round_var(result, rscale);
 
@@ -4609,7 +5157,7 @@ exp_var_internal(NumericVar *arg, NumericVar *result, int rscale)
                add_var(&ni, &const_one, &ni);
                mul_var(&xpow, &x, &xpow, local_rscale);
                mul_var(&ifac, &ni, &ifac, 0);
-               div_var(&xpow, &ifac, &elem, local_rscale, true);
+               div_var_fast(&xpow, &ifac, &elem, local_rscale, true);
 
                if (elem.ndigits == 0)
                        break;
@@ -4693,7 +5241,7 @@ ln_var(NumericVar *arg, NumericVar *result, int rscale)
         */
        sub_var(&x, &const_one, result);
        add_var(&x, &const_one, &elem);
-       div_var(result, &elem, result, local_rscale, true);
+       div_var_fast(result, &elem, result, local_rscale, true);
        set_var_from_var(result, &xx);
        mul_var(result, result, &x, local_rscale);
 
@@ -4703,7 +5251,7 @@ ln_var(NumericVar *arg, NumericVar *result, int rscale)
        {
                add_var(&ni, &const_two, &ni);
                mul_var(&xx, &x, &xx, local_rscale);
-               div_var(&xx, &ni, &elem, local_rscale, true);
+               div_var_fast(&xx, &ni, &elem, local_rscale, true);
 
                if (elem.ndigits == 0)
                        break;
@@ -4773,7 +5321,7 @@ log_var(NumericVar *base, NumericVar *num, NumericVar *result)
        /* Select scale for division result */
        rscale = select_div_scale(&ln_num, &ln_base);
 
-       div_var(&ln_num, &ln_base, result, rscale, true);
+       div_var_fast(&ln_num, &ln_base, result, rscale, true);
 
        free_var(&ln_num);
        free_var(&ln_base);
@@ -4829,6 +5377,17 @@ power_var(NumericVar *base, NumericVar *exp, NumericVar *result)
                free_var(&x);
        }
 
+       /*
+        * This avoids log(0) for cases of 0 raised to a non-integer. 0 ^ 0
+        * handled by power_var_int().
+        */
+       if (cmp_var(base, &const_zero) == 0)
+       {
+               set_var_from_var(&const_zero, result);
+               result->dscale = NUMERIC_MIN_SIG_DIGITS;                /* no need to round */
+               return;
+       }
+
        init_var(&ln_base);
        init_var(&ln_num);
 
@@ -4893,15 +5452,17 @@ power_var_int(NumericVar *base, int exp, NumericVar *result, int rscale)
        NumericVar      base_prod;
        int                     local_rscale;
 
-       /* Detect some special cases, particularly 0^0. */
-
        switch (exp)
        {
                case 0:
-                       if (base->ndigits == 0)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_FLOATING_POINT_EXCEPTION),
-                                                errmsg("zero raised to zero is undefined")));
+
+                       /*
+                        * While 0 ^ 0 can be either 1 or indeterminate (error), we treat
+                        * it as 1 because most programming languages do this. SQL:2003
+                        * also requires a return value of 1.
+                        * http://en.wikipedia.org/wiki/Exponentiation#Zero_to_the_zero_pow
+                        * er
+                        */
                        set_var_from_var(&const_one, result);
                        result->dscale = rscale;        /* no need to round */
                        return;
@@ -4947,7 +5508,7 @@ power_var_int(NumericVar *base, int exp, NumericVar *result, int rscale)
 
        /* Compensate for input sign, and round to requested rscale */
        if (neg)
-               div_var(&const_one, result, result, rscale, true);
+               div_var_fast(&const_one, result, result, rscale, true);
        else
                round_var(result, rscale);
 }
@@ -5318,8 +5879,8 @@ round_var(NumericVar *var, int rscale)
 /*
  * trunc_var
  *
- * Truncate the value of a variable at rscale decimal digits after the
- * decimal point.  NOTE: we allow rscale < 0 here, implying
+ * Truncate (towards zero) the value of a variable at rscale decimal digits
+ * after the decimal point.  NOTE: we allow rscale < 0 here, implying
  * truncation before the decimal point.
  */
 static void