X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Futils%2Fadt%2Fnumeric.c;h=2892b5d2fbf0759be09ee6b21daa5ce952c5836c;hb=901be0fad4034c9cf8a3588fd6cf2ece82e4b8ce;hp=86765d5d5325f686046f46d152625f181a1df303;hpb=a0fad9762a22e739de69c85b51ff7a47e672732f;p=postgresql diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 86765d5d53..2892b5d2fb 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -11,10 +11,10 @@ * 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 - * 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) { @@ -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;