Skip to content

Commit dcd0ab6

Browse files
committed
Fix corner-case errors and loss of precision in numeric_power().
This fixes a couple of related problems that arise when raising numbers to very large powers. Firstly, when raising a negative number to a very large integer power, the result should be well-defined, but the previous code would only cope if the exponent was small enough to go through power_var_int(). Otherwise it would throw an internal error, attempting to take the logarithm of a negative number. Fix this by adding suitable handling to the general case in power_var() to cope with negative bases, checking for integer powers there. Next, when raising a (positive or negative) number whose absolute value is slightly less than 1 to a very large power, the result should approach zero as the power is increased. However, in some cases, for sufficiently large powers, this would lose all precision and return 1 instead of 0. This was due to the way that the local_rscale was being calculated for the final full-precision calculation: local_rscale = rscale + (int) val - ln_dweight + 8 The first two terms on the right hand side are meant to give the number of significant digits required in the result ("val" being the estimated result weight). However, this failed to account for the fact that rscale is clipped to a maximum of NUMERIC_MAX_DISPLAY_SCALE (1000), and the result weight might be less then -1000, causing their sum to be negative, leading to a loss of precision. Fix this by forcing the number of significant digits calculated to be nonnegative. It's OK for it to be zero (when the result weight is less than -1000), since the local_rscale value then includes a few extra digits to ensure an accurate result. Finally, add additional underflow checks to exp_var() and power_var(), so that they consistently return zero for cases like this where the result is indistinguishable from zero. Some paths through this code already returned zero in such cases, but others were throwing overflow errors. Dean Rasheed, reviewed by Yugo Nagata. Discussion: http://postgr.es/m/CAEZATCW6Dvq7+3wN3tt5jLj-FyOcUgT5xNoOqce5=6Su0bCR0w@mail.gmail.com
1 parent 9455e7f commit dcd0ab6

File tree

3 files changed

+132
-16
lines changed

3 files changed

+132
-16
lines changed

src/backend/utils/adt/numeric.c

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3016,20 +3016,16 @@ numeric_power(PG_FUNCTION_ARGS)
30163016
/*
30173017
* The SQL spec requires that we emit a particular SQLSTATE error code for
30183018
* certain error conditions. Specifically, we don't return a
3019-
* divide-by-zero error code for 0 ^ -1.
3019+
* divide-by-zero error code for 0 ^ -1. Raising a negative number to a
3020+
* non-integer power must produce the same error code, but that case is
3021+
* handled in power_var().
30203022
*/
30213023
if (cmp_var(&arg1, &const_zero) == 0 &&
30223024
cmp_var(&arg2, &const_zero) < 0)
30233025
ereport(ERROR,
30243026
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
30253027
errmsg("zero raised to a negative power is undefined")));
30263028

3027-
if (cmp_var(&arg1, &const_zero) < 0 &&
3028-
cmp_var(&arg2, &arg2_trunc) != 0)
3029-
ereport(ERROR,
3030-
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
3031-
errmsg("a negative number raised to a non-integer power yields a complex result")));
3032-
30333029
/*
30343030
* Call power_var() to compute and return the result; note it handles
30353031
* scale selection itself.
@@ -7878,12 +7874,18 @@ exp_var(const NumericVar *arg, NumericVar *result, int rscale)
78787874
*/
78797875
val = numericvar_to_double_no_overflow(&x);
78807876

7881-
/* Guard against overflow */
7877+
/* Guard against overflow/underflow */
78827878
/* If you change this limit, see also power_var()'s limit */
78837879
if (Abs(val) >= NUMERIC_MAX_RESULT_SCALE * 3)
7884-
ereport(ERROR,
7885-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
7886-
errmsg("value overflows numeric format")));
7880+
{
7881+
if (val > 0)
7882+
ereport(ERROR,
7883+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
7884+
errmsg("value overflows numeric format")));
7885+
zero_var(result);
7886+
result->dscale = rscale;
7887+
return;
7888+
}
78877889

78887890
/* decimal weight = log10(e^x) = x * log10(e) */
78897891
dweight = (int) (val * 0.434294481903252);
@@ -8228,10 +8230,13 @@ log_var(const NumericVar *base, const NumericVar *num, NumericVar *result)
82288230
static void
82298231
power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
82308232
{
8233+
int res_sign;
8234+
NumericVar abs_base;
82318235
NumericVar ln_base;
82328236
NumericVar ln_num;
82338237
int ln_dweight;
82348238
int rscale;
8239+
int sig_digits;
82358240
int local_rscale;
82368241
double val;
82378242

@@ -8271,9 +8276,40 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
82718276
return;
82728277
}
82738278

8279+
init_var(&abs_base);
82748280
init_var(&ln_base);
82758281
init_var(&ln_num);
82768282

8283+
/*
8284+
* If base is negative, insist that exp be an integer. The result is then
8285+
* positive if exp is even and negative if exp is odd.
8286+
*/
8287+
if (base->sign == NUMERIC_NEG)
8288+
{
8289+
/*
8290+
* Check that exp is an integer. This error code is defined by the
8291+
* SQL standard, and matches other errors in numeric_power().
8292+
*/
8293+
if (exp->ndigits > 0 && exp->ndigits > exp->weight + 1)
8294+
ereport(ERROR,
8295+
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
8296+
errmsg("a negative number raised to a non-integer power yields a complex result")));
8297+
8298+
/* Test if exp is odd or even */
8299+
if (exp->ndigits > 0 && exp->ndigits == exp->weight + 1 &&
8300+
(exp->digits[exp->ndigits - 1] & 1))
8301+
res_sign = NUMERIC_NEG;
8302+
else
8303+
res_sign = NUMERIC_POS;
8304+
8305+
/* Then work with abs(base) below */
8306+
set_var_from_var(base, &abs_base);
8307+
abs_base.sign = NUMERIC_POS;
8308+
base = &abs_base;
8309+
}
8310+
else
8311+
res_sign = NUMERIC_POS;
8312+
82778313
/*----------
82788314
* Decide on the scale for the ln() calculation. For this we need an
82798315
* estimate of the weight of the result, which we obtain by doing an
@@ -8304,11 +8340,17 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
83048340

83058341
val = numericvar_to_double_no_overflow(&ln_num);
83068342

8307-
/* initial overflow test with fuzz factor */
8343+
/* initial overflow/underflow test with fuzz factor */
83088344
if (Abs(val) > NUMERIC_MAX_RESULT_SCALE * 3.01)
8309-
ereport(ERROR,
8310-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
8311-
errmsg("value overflows numeric format")));
8345+
{
8346+
if (val > 0)
8347+
ereport(ERROR,
8348+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
8349+
errmsg("value overflows numeric format")));
8350+
zero_var(result);
8351+
result->dscale = NUMERIC_MAX_DISPLAY_SCALE;
8352+
return;
8353+
}
83128354

83138355
val *= 0.434294481903252; /* approximate decimal result weight */
83148356

@@ -8319,8 +8361,12 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
83198361
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
83208362
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
83218363

8364+
/* significant digits required in the result */
8365+
sig_digits = rscale + (int) val;
8366+
sig_digits = Max(sig_digits, 0);
8367+
83228368
/* set the scale for the real exp * ln(base) calculation */
8323-
local_rscale = rscale + (int) val - ln_dweight + 8;
8369+
local_rscale = sig_digits - ln_dweight + 8;
83248370
local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE);
83258371

83268372
/* and do the real calculation */
@@ -8331,8 +8377,12 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
83318377

83328378
exp_var(&ln_num, result, rscale);
83338379

8380+
if (res_sign == NUMERIC_NEG && result->ndigits > 0)
8381+
result->sign = NUMERIC_NEG;
8382+
83348383
free_var(&ln_num);
83358384
free_var(&ln_base);
8385+
free_var(&abs_base);
83368386
}
83378387

83388388
/*

src/test/regress/expected/numeric.out

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,12 @@ select 1.000000000123 ^ (-2147483648);
16351635
0.7678656556403084
16361636
(1 row)
16371637

1638+
select 0.9999999999 ^ 23300000000000 = 0 as rounds_to_zero;
1639+
rounds_to_zero
1640+
----------------
1641+
t
1642+
(1 row)
1643+
16381644
-- cases that used to error out
16391645
select 0.12 ^ (-25);
16401646
?column?
@@ -1648,6 +1654,43 @@ select 0.5678 ^ (-85);
16481654
782333637740774446257.7719390061997396
16491655
(1 row)
16501656

1657+
select 0.9999999999 ^ 70000000000000 = 0 as underflows;
1658+
underflows
1659+
------------
1660+
t
1661+
(1 row)
1662+
1663+
-- negative base to integer powers
1664+
select (-1.0) ^ 2147483646;
1665+
?column?
1666+
--------------------
1667+
1.0000000000000000
1668+
(1 row)
1669+
1670+
select (-1.0) ^ 2147483647;
1671+
?column?
1672+
---------------------
1673+
-1.0000000000000000
1674+
(1 row)
1675+
1676+
select (-1.0) ^ 2147483648;
1677+
?column?
1678+
--------------------
1679+
1.0000000000000000
1680+
(1 row)
1681+
1682+
select (-1.0) ^ 1000000000000000;
1683+
?column?
1684+
--------------------
1685+
1.0000000000000000
1686+
(1 row)
1687+
1688+
select (-1.0) ^ 1000000000000001;
1689+
?column?
1690+
---------------------
1691+
-1.0000000000000000
1692+
(1 row)
1693+
16511694
--
16521695
-- Tests for raising to non-integer powers
16531696
--
@@ -1766,6 +1809,18 @@ select exp(1.0::numeric(71,70));
17661809
2.7182818284590452353602874713526624977572470936999595749669676277240766
17671810
(1 row)
17681811

1812+
select exp(-5000::numeric) = 0 as rounds_to_zero;
1813+
rounds_to_zero
1814+
----------------
1815+
t
1816+
(1 row)
1817+
1818+
select exp(-10000::numeric) = 0 as underflows;
1819+
underflows
1820+
------------
1821+
t
1822+
(1 row)
1823+
17691824
-- cases that used to generate inaccurate results
17701825
select exp(32.999);
17711826
exp

src/test/regress/sql/numeric.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,10 +899,19 @@ select 3.789 ^ 35;
899899
select 1.2 ^ 345;
900900
select 0.12 ^ (-20);
901901
select 1.000000000123 ^ (-2147483648);
902+
select 0.9999999999 ^ 23300000000000 = 0 as rounds_to_zero;
902903

903904
-- cases that used to error out
904905
select 0.12 ^ (-25);
905906
select 0.5678 ^ (-85);
907+
select 0.9999999999 ^ 70000000000000 = 0 as underflows;
908+
909+
-- negative base to integer powers
910+
select (-1.0) ^ 2147483646;
911+
select (-1.0) ^ 2147483647;
912+
select (-1.0) ^ 2147483648;
913+
select (-1.0) ^ 1000000000000000;
914+
select (-1.0) ^ 1000000000000001;
906915

907916
--
908917
-- Tests for raising to non-integer powers
@@ -942,6 +951,8 @@ select 1.234 ^ 5678;
942951
select exp(0.0);
943952
select exp(1.0);
944953
select exp(1.0::numeric(71,70));
954+
select exp(-5000::numeric) = 0 as rounds_to_zero;
955+
select exp(-10000::numeric) = 0 as underflows;
945956

946957
-- cases that used to generate inaccurate results
947958
select exp(32.999);

0 commit comments

Comments
 (0)