Skip to content

Commit 228ed43

Browse files
committed
Fix power_var_int() for large integer exponents.
The code for raising a NUMERIC value to an integer power wasn't very careful about large powers. It got an outright wrong answer for an exponent of INT_MIN, due to failure to consider overflow of the Abs(exp) operation; which is fixable by using an unsigned rather than signed exponent value after that point. Also, even though the number of iterations of the power-computation loop is pretty limited, it's easy for the repeated squarings to result in ridiculously enormous intermediate values, which can take unreasonable amounts of time/memory to process, or even overflow the internal "weight" field and so produce a wrong answer. We can forestall misbehaviors of that sort by bailing out as soon as the weight value exceeds what will fit in int16, since then the final answer must overflow (if exp > 0) or underflow (if exp < 0) the packed numeric format. Per off-list report from Pavel Stehule. Back-patch to all supported branches.
1 parent 7679eff commit 228ed43

File tree

3 files changed

+54
-4
lines changed

3 files changed

+54
-4
lines changed

src/backend/utils/adt/numeric.c

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5696,10 +5696,12 @@ power_var(NumericVar *base, NumericVar *exp, NumericVar *result)
56965696
static void
56975697
power_var_int(NumericVar *base, int exp, NumericVar *result, int rscale)
56985698
{
5699+
unsigned int mask;
56995700
bool neg;
57005701
NumericVar base_prod;
57015702
int local_rscale;
57025703

5704+
/* Handle some common special cases, as well as corner cases */
57035705
switch (exp)
57045706
{
57055707
case 0:
@@ -5733,23 +5735,43 @@ power_var_int(NumericVar *base, int exp, NumericVar *result, int rscale)
57335735
* pattern of exp. We do the multiplications with some extra precision.
57345736
*/
57355737
neg = (exp < 0);
5736-
exp = Abs(exp);
5738+
mask = Abs(exp);
57375739

57385740
local_rscale = rscale + MUL_GUARD_DIGITS * 2;
57395741

57405742
init_var(&base_prod);
57415743
set_var_from_var(base, &base_prod);
57425744

5743-
if (exp & 1)
5745+
if (mask & 1)
57445746
set_var_from_var(base, result);
57455747
else
57465748
set_var_from_var(&const_one, result);
57475749

5748-
while ((exp >>= 1) > 0)
5750+
while ((mask >>= 1) > 0)
57495751
{
57505752
mul_var(&base_prod, &base_prod, &base_prod, local_rscale);
5751-
if (exp & 1)
5753+
if (mask & 1)
57525754
mul_var(&base_prod, result, result, local_rscale);
5755+
5756+
/*
5757+
* When abs(base) > 1, the number of digits to the left of the decimal
5758+
* point in base_prod doubles at each iteration, so if exp is large we
5759+
* could easily spend large amounts of time and memory space doing the
5760+
* multiplications. But once the weight exceeds what will fit in
5761+
* int16, the final result is guaranteed to overflow (or underflow, if
5762+
* exp < 0), so we can give up before wasting too many cycles.
5763+
*/
5764+
if (base_prod.weight > SHRT_MAX || result->weight > SHRT_MAX)
5765+
{
5766+
/* overflow, unless neg, in which case result should be 0 */
5767+
if (!neg)
5768+
ereport(ERROR,
5769+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
5770+
errmsg("value overflows numeric format")));
5771+
zero_var(result);
5772+
neg = false;
5773+
break;
5774+
}
57535775
}
57545776

57555777
free_var(&base_prod);

src/test/regress/expected/numeric.out

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,3 +1390,22 @@ select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123;
13901390
12345678901234567890
13911391
(1 row)
13921392

1393+
--
1394+
-- Test code path for raising to integer powers
1395+
--
1396+
select 10.0 ^ -2147483648 as rounds_to_zero;
1397+
rounds_to_zero
1398+
--------------------
1399+
0.0000000000000000
1400+
(1 row)
1401+
1402+
select 10.0 ^ -2147483647 as rounds_to_zero;
1403+
rounds_to_zero
1404+
--------------------
1405+
0.0000000000000000
1406+
(1 row)
1407+
1408+
select 10.0 ^ 2147483647 as overflows;
1409+
ERROR: value overflows numeric format
1410+
select 117743296169.0 ^ 1000000000 as overflows;
1411+
ERROR: value overflows numeric format

src/test/regress/sql/numeric.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,3 +828,12 @@ select 12345678901234567890 % 123;
828828
select 12345678901234567890 / 123;
829829
select div(12345678901234567890, 123);
830830
select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123;
831+
832+
--
833+
-- Test code path for raising to integer powers
834+
--
835+
836+
select 10.0 ^ -2147483648 as rounds_to_zero;
837+
select 10.0 ^ -2147483647 as rounds_to_zero;
838+
select 10.0 ^ 2147483647 as overflows;
839+
select 117743296169.0 ^ 1000000000 as overflows;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy