Skip to content

Commit 40c7fcb

Browse files
committed
Improve the accuracy of numeric power() for integer exponents.
This makes the choice of result scale of numeric power() for integer exponents consistent with the choice for non-integer exponents, and with the result scale of other numeric functions. Specifically, the result scale will be at least as large as the scale of either input, and sufficient to ensure that the result has at least 16 significant digits. Formerly, the result scale was based only on the scale of the first input, without taking into account the weight of the result. For results with negative weight, that could lead to results with very few or even no non-zero significant digits (e.g., 10.0 ^ (-18) produced 0.0000000000000000). Fix this by moving responsibility for the choice of result scale into power_var_int(), which already has code to estimate the result weight. Per report by Adrian Klaver and suggested fix by Tom Lane. No back-patch -- arguably this is a bug fix, but one which is easy to work around, so it doesn't seem worth the risk of changing query results in stable branches. Discussion: https://postgr.es/m/12a40226-70ac-3a3b-3d3a-fdaf9e32d312%40aklaver.com
1 parent 7fd1ae9 commit 40c7fcb

File tree

5 files changed

+420
-344
lines changed

5 files changed

+420
-344
lines changed

src/backend/utils/adt/numeric.c

+67-43
Original file line numberDiff line numberDiff line change
@@ -571,8 +571,8 @@ static void log_var(const NumericVar *base, const NumericVar *num,
571571
NumericVar *result);
572572
static void power_var(const NumericVar *base, const NumericVar *exp,
573573
NumericVar *result);
574-
static void power_var_int(const NumericVar *base, int exp, NumericVar *result,
575-
int rscale);
574+
static void power_var_int(const NumericVar *base, int exp, int exp_dscale,
575+
NumericVar *result);
576576
static void power_ten_int(int exp, NumericVar *result);
577577

578578
static int cmp_abs(const NumericVar *var1, const NumericVar *var2);
@@ -10335,13 +10335,8 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1033510335
{
1033610336
if (expval64 >= PG_INT32_MIN && expval64 <= PG_INT32_MAX)
1033710337
{
10338-
/* Okay, select rscale */
10339-
rscale = NUMERIC_MIN_SIG_DIGITS;
10340-
rscale = Max(rscale, base->dscale);
10341-
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
10342-
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
10343-
10344-
power_var_int(base, (int) expval64, result, rscale);
10338+
/* Okay, use power_var_int */
10339+
power_var_int(base, (int) expval64, exp->dscale, result);
1034510340
return;
1034610341
}
1034710342
}
@@ -10475,19 +10470,76 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1047510470
* power_var_int() -
1047610471
*
1047710472
* Raise base to the power of exp, where exp is an integer.
10473+
*
10474+
* Note: this routine chooses dscale of the result.
1047810475
*/
1047910476
static void
10480-
power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
10477+
power_var_int(const NumericVar *base, int exp, int exp_dscale,
10478+
NumericVar *result)
1048110479
{
1048210480
double f;
1048310481
int p;
1048410482
int i;
10483+
int rscale;
1048510484
int sig_digits;
1048610485
unsigned int mask;
1048710486
bool neg;
1048810487
NumericVar base_prod;
1048910488
int local_rscale;
1049010489

10490+
/*
10491+
* Choose the result scale. For this we need an estimate of the decimal
10492+
* weight of the result, which we obtain by approximating using double
10493+
* precision arithmetic.
10494+
*
10495+
* We also perform crude overflow/underflow tests here so that we can exit
10496+
* early if the result is sure to overflow/underflow, and to guard against
10497+
* integer overflow when choosing the result scale.
10498+
*/
10499+
if (base->ndigits != 0)
10500+
{
10501+
/*----------
10502+
* Choose f (double) and p (int) such that base ~= f * 10^p.
10503+
* Then log10(result) = log10(base^exp) ~= exp * (log10(f) + p).
10504+
*----------
10505+
*/
10506+
f = base->digits[0];
10507+
p = base->weight * DEC_DIGITS;
10508+
10509+
for (i = 1; i < base->ndigits && i * DEC_DIGITS < 16; i++)
10510+
{
10511+
f = f * NBASE + base->digits[i];
10512+
p -= DEC_DIGITS;
10513+
}
10514+
10515+
f = exp * (log10(f) + p); /* approximate decimal result weight */
10516+
}
10517+
else
10518+
f = 0; /* result is 0 or 1 (weight 0), or error */
10519+
10520+
/* overflow/underflow tests with fuzz factors */
10521+
if (f > (SHRT_MAX + 1) * DEC_DIGITS)
10522+
ereport(ERROR,
10523+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
10524+
errmsg("value overflows numeric format")));
10525+
if (f + 1 < -NUMERIC_MAX_DISPLAY_SCALE)
10526+
{
10527+
zero_var(result);
10528+
result->dscale = NUMERIC_MAX_DISPLAY_SCALE;
10529+
return;
10530+
}
10531+
10532+
/*
10533+
* Choose the result scale in the same way as power_var(), so it has at
10534+
* least NUMERIC_MIN_SIG_DIGITS significant digits and is not less than
10535+
* either input's display scale.
10536+
*/
10537+
rscale = NUMERIC_MIN_SIG_DIGITS - (int) f;
10538+
rscale = Max(rscale, base->dscale);
10539+
rscale = Max(rscale, exp_dscale);
10540+
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
10541+
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
10542+
1049110543
/* Handle some common special cases, as well as corner cases */
1049210544
switch (exp)
1049310545
{
@@ -10532,43 +10584,15 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
1053210584
* The general case repeatedly multiplies base according to the bit
1053310585
* pattern of exp.
1053410586
*
10535-
* First we need to estimate the weight of the result so that we know how
10536-
* many significant digits are needed.
10587+
* The local rscale used for each multiplication is varied to keep a fixed
10588+
* number of significant digits, sufficient to give the required result
10589+
* scale.
1053710590
*/
10538-
f = base->digits[0];
10539-
p = base->weight * DEC_DIGITS;
10540-
10541-
for (i = 1; i < base->ndigits && i * DEC_DIGITS < 16; i++)
10542-
{
10543-
f = f * NBASE + base->digits[i];
10544-
p -= DEC_DIGITS;
10545-
}
10546-
10547-
/*----------
10548-
* We have base ~= f * 10^p
10549-
* so log10(result) = log10(base^exp) ~= exp * (log10(f) + p)
10550-
*----------
10551-
*/
10552-
f = exp * (log10(f) + p);
10553-
10554-
/*
10555-
* Apply crude overflow/underflow tests so we can exit early if the result
10556-
* certainly will overflow/underflow.
10557-
*/
10558-
if (f > 3 * SHRT_MAX * DEC_DIGITS)
10559-
ereport(ERROR,
10560-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
10561-
errmsg("value overflows numeric format")));
10562-
if (f + 1 < -rscale || f + 1 < -NUMERIC_MAX_DISPLAY_SCALE)
10563-
{
10564-
zero_var(result);
10565-
result->dscale = rscale;
10566-
return;
10567-
}
1056810591

1056910592
/*
1057010593
* Approximate number of significant digits in the result. Note that the
10571-
* underflow test above means that this is necessarily >= 0.
10594+
* underflow test above, together with the choice of rscale, ensures that
10595+
* this approximation is necessarily > 0.
1057210596
*/
1057310597
sig_digits = 1 + rscale + (int) f;
1057410598

0 commit comments

Comments
 (0)