diff options
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 169 |
1 files changed, 142 insertions, 27 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 67e5bc28bc..a36fb6330b 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-2009, PostgreSQL Global Development Group + * Copyright (c) 1998-2010, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.118 2009/06/11 14:49:03 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/numeric.c,v 1.123 2010/02/26 02:01:09 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -247,6 +247,7 @@ static const char *set_var_from_str(const char *str, const char *cp, 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); @@ -427,6 +428,32 @@ numeric_out(PG_FUNCTION_ARGS) } /* + * 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 * * External format is a sequence of int16's: @@ -2651,16 +2678,14 @@ int2_sum(PG_FUNCTION_ARGS) } /* - * If we're invoked by nodeAgg, we can cheat and modify our first + * If we're invoked as an aggregate, 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.) */ #ifndef USE_FLOAT8_BYVAL /* controls int8 too */ - if (fcinfo->context && - (IsA(fcinfo->context, AggState) || - IsA(fcinfo->context, WindowAggState))) + if (AggCheckCallContext(fcinfo, NULL)) { int64 *oldsum = (int64 *) PG_GETARG_POINTER(0); @@ -2702,16 +2727,14 @@ int4_sum(PG_FUNCTION_ARGS) } /* - * If we're invoked by nodeAgg, we can cheat and modify our first + * If we're invoked as an aggregate, 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.) */ #ifndef USE_FLOAT8_BYVAL /* controls int8 too */ - if (fcinfo->context && - (IsA(fcinfo->context, AggState) || - IsA(fcinfo->context, WindowAggState))) + if (AggCheckCallContext(fcinfo, NULL)) { int64 *oldsum = (int64 *) PG_GETARG_POINTER(0); @@ -2754,7 +2777,7 @@ int8_sum(PG_FUNCTION_ARGS) } /* - * Note that we cannot special-case the nodeAgg case here, as we do for + * Note that we cannot special-case the aggregate case here, as we do for * int2_sum and int4_sum: numeric is of variable size, so we cannot modify * our first parameter in-place. */ @@ -2780,16 +2803,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 @@ -2800,13 +2815,11 @@ int2_avg_accum(PG_FUNCTION_ARGS) Int8TransTypeData *transdata; /* - * If we're invoked by nodeAgg, we can cheat and modify our first + * If we're invoked as an aggregate, we can cheat and modify our first * 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) || - IsA(fcinfo->context, WindowAggState))) + if (AggCheckCallContext(fcinfo, NULL)) transarray = PG_GETARG_ARRAYTYPE_P(0); else transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); @@ -2830,13 +2843,11 @@ int4_avg_accum(PG_FUNCTION_ARGS) Int8TransTypeData *transdata; /* - * If we're invoked by nodeAgg, we can cheat and modify our first + * If we're invoked as an aggregate, we can cheat and modify our first * 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) || - IsA(fcinfo->context, WindowAggState))) + if (AggCheckCallContext(fcinfo, NULL)) transarray = PG_GETARG_ARRAYTYPE_P(0); else transarray = PG_GETARG_ARRAYTYPE_P_COPY(0); @@ -3363,6 +3374,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() - |