diff options
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 110 |
1 files changed, 67 insertions, 43 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index cafe1ac47b0..7f0e93aa803 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -571,8 +571,8 @@ static void log_var(const NumericVar *base, const NumericVar *num, NumericVar *result); static void power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result); -static void power_var_int(const NumericVar *base, int exp, NumericVar *result, - int rscale); +static void power_var_int(const NumericVar *base, int exp, int exp_dscale, + NumericVar *result); static void power_ten_int(int exp, NumericVar *result); static int cmp_abs(const NumericVar *var1, const NumericVar *var2); @@ -10335,13 +10335,8 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result) { if (expval64 >= PG_INT32_MIN && expval64 <= PG_INT32_MAX) { - /* Okay, select rscale */ - rscale = NUMERIC_MIN_SIG_DIGITS; - rscale = Max(rscale, base->dscale); - rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); - rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); - - power_var_int(base, (int) expval64, result, rscale); + /* Okay, use power_var_int */ + power_var_int(base, (int) expval64, exp->dscale, result); return; } } @@ -10475,19 +10470,76 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result) * power_var_int() - * * Raise base to the power of exp, where exp is an integer. + * + * Note: this routine chooses dscale of the result. */ static void -power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale) +power_var_int(const NumericVar *base, int exp, int exp_dscale, + NumericVar *result) { double f; int p; int i; + int rscale; int sig_digits; unsigned int mask; bool neg; NumericVar base_prod; int local_rscale; + /* + * Choose the result scale. For this we need an estimate of the decimal + * weight of the result, which we obtain by approximating using double + * precision arithmetic. + * + * We also perform crude overflow/underflow tests here so that we can exit + * early if the result is sure to overflow/underflow, and to guard against + * integer overflow when choosing the result scale. + */ + if (base->ndigits != 0) + { + /*---------- + * Choose f (double) and p (int) such that base ~= f * 10^p. + * Then log10(result) = log10(base^exp) ~= exp * (log10(f) + p). + *---------- + */ + f = base->digits[0]; + p = base->weight * DEC_DIGITS; + + for (i = 1; i < base->ndigits && i * DEC_DIGITS < 16; i++) + { + f = f * NBASE + base->digits[i]; + p -= DEC_DIGITS; + } + + f = exp * (log10(f) + p); /* approximate decimal result weight */ + } + else + f = 0; /* result is 0 or 1 (weight 0), or error */ + + /* overflow/underflow tests with fuzz factors */ + if (f > (SHRT_MAX + 1) * DEC_DIGITS) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + if (f + 1 < -NUMERIC_MAX_DISPLAY_SCALE) + { + zero_var(result); + result->dscale = NUMERIC_MAX_DISPLAY_SCALE; + return; + } + + /* + * Choose the result scale in the same way as power_var(), so it has at + * least NUMERIC_MIN_SIG_DIGITS significant digits and is not less than + * either input's display scale. + */ + rscale = NUMERIC_MIN_SIG_DIGITS - (int) f; + rscale = Max(rscale, base->dscale); + rscale = Max(rscale, exp_dscale); + rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE); + rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE); + /* Handle some common special cases, as well as corner cases */ switch (exp) { @@ -10532,43 +10584,15 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale) * The general case repeatedly multiplies base according to the bit * pattern of exp. * - * First we need to estimate the weight of the result so that we know how - * many significant digits are needed. + * The local rscale used for each multiplication is varied to keep a fixed + * number of significant digits, sufficient to give the required result + * scale. */ - f = base->digits[0]; - p = base->weight * DEC_DIGITS; - - for (i = 1; i < base->ndigits && i * DEC_DIGITS < 16; i++) - { - f = f * NBASE + base->digits[i]; - p -= DEC_DIGITS; - } - - /*---------- - * We have base ~= f * 10^p - * so log10(result) = log10(base^exp) ~= exp * (log10(f) + p) - *---------- - */ - f = exp * (log10(f) + p); - - /* - * Apply crude overflow/underflow tests so we can exit early if the result - * certainly will overflow/underflow. - */ - if (f > 3 * SHRT_MAX * DEC_DIGITS) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value overflows numeric format"))); - if (f + 1 < -rscale || f + 1 < -NUMERIC_MAX_DISPLAY_SCALE) - { - zero_var(result); - result->dscale = rscale; - return; - } /* * Approximate number of significant digits in the result. Note that the - * underflow test above means that this is necessarily >= 0. + * underflow test above, together with the choice of rscale, ensures that + * this approximation is necessarily > 0. */ sig_digits = 1 + rscale + (int) f; |