* 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.113 2008/05/09 15:36:06 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.121 2010/01/07 04:53:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* ----------
- * 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.
*
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);
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);
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);
}
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
*
/*
* 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.
+ * 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
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);
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);
/*
* 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.
- * (If int8 is pass-by-value, then of course this is useless as well
- * as incorrect, so just ifdef it out.)
+ * 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.)
*/
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
- if (fcinfo->context && IsA(fcinfo->context, AggState))
+ if (fcinfo->context &&
+ (IsA(fcinfo->context, AggState) ||
+ IsA(fcinfo->context, WindowAggState)))
{
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
/*
* 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.
- * (If int8 is pass-by-value, then of course this is useless as well
- * as incorrect, so just ifdef it out.)
+ * 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.)
*/
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
- if (fcinfo->context && IsA(fcinfo->context, AggState))
+ if (fcinfo->context &&
+ (IsA(fcinfo->context, AggState) ||
+ IsA(fcinfo->context, WindowAggState)))
{
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
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
* 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);
* 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);
* 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;
* 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 '+':
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
/* Strip any leading/trailing zeroes, and normalize weight if zero */
strip_var(dest);
+
+ /* Return end+1 position for caller */
+ return cp;
}
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() -
/* 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
/*
* 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
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;
*
* 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--)
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
- * a 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)
{
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 */
}
/*
- * This avoids log(0) for cases of 0 raised to a non-integer.
- * 0 ^ 0 handled by power_var_int().
+ * 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 */
+ result->dscale = NUMERIC_MIN_SIG_DIGITS; /* no need to round */
return;
}
-
+
init_var(&ln_base);
init_var(&ln_num);
switch (exp)
{
case 0:
+
/*
- * 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_power
+ * 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 */