]> 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 86765d5d5325f686046f46d152625f181a1df303..2892b5d2fbf0759be09ee6b21daa5ce952c5836c 100644 (file)
  * Transactions on Mathematical Software, Vol. 24, No. 4, December 1998,
  * pages 359-367.
  *
- * Copyright (c) 1998-2008, PostgreSQL Global Development Group
+ * Copyright (c) 1998-2010, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.109 2008/04/04 18:45:36 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.121 2010/01/07 04:53:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -90,7 +90,7 @@ typedef int16 NumericDigit;
 
 
 /* ----------
- * NumericVar is the format we use for arithmetic.  The digit-array part
+ * 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.
  *
@@ -242,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);
 
@@ -270,7 +272,7 @@ static void mul_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
 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);
+                        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);
@@ -321,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);
 }
@@ -382,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
  *
@@ -1893,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
@@ -2116,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);
@@ -2176,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);
@@ -2599,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);
 
@@ -2614,6 +2699,7 @@ int2_sum(PG_FUNCTION_ARGS)
                PG_RETURN_POINTER(oldsum);
        }
        else
+#endif
        {
                int64           oldsum = PG_GETARG_INT64(0);
 
@@ -2644,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);
 
@@ -2659,6 +2750,7 @@ int4_sum(PG_FUNCTION_ARGS)
                PG_RETURN_POINTER(oldsum);
        }
        else
+#endif
        {
                int64           oldsum = PG_GETARG_INT64(0);
 
@@ -2716,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
@@ -2740,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);
@@ -2768,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);
@@ -2951,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;
@@ -2972,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 '+':
@@ -3065,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
@@ -3116,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;
 }
 
 
@@ -3306,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() -
@@ -4151,6 +4332,7 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
        /* 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
@@ -4164,8 +4346,8 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
        /*
         * 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).  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
@@ -4186,8 +4368,8 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
        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).
+                * 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;
@@ -4206,12 +4388,12 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
                 *
                 * 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
+                * 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);
+                       int                     d = NBASE / (divisor[1] + 1);
 
                        carry = 0;
                        for (i = var2ndigits; i > 0; i--)
@@ -4237,22 +4419,22 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
                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
+                * 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;
+                       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
-                        * dividend.
+                        * 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)
                        {
@@ -4264,14 +4446,15 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result,
                                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.)
+                        * 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])
+                                  (next2digits - qhat * divisor1) * NBASE + dividend[j + 2])
                                qhat--;
 
                        /* As above, need do nothing more when quotient digit is 0 */
@@ -5194,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);
 
@@ -5258,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;