X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Futils%2Fadt%2Fnumeric.c;h=2892b5d2fbf0759be09ee6b21daa5ce952c5836c;hb=901be0fad4034c9cf8a3588fd6cf2ece82e4b8ce;hp=f173f30211c7a62d414e2d4f18bc2165223d7e44;hpb=234a02b2a888cacc4c09363cc1411ae4eac9bb51;p=postgresql diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index f173f30211..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-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 $ * *------------------------------------------------------------------------- */ @@ -26,8 +26,10 @@ #include #include +#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