diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 678f82f2a5c..c6b00ef238c 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -5696,10 +5696,12 @@ power_var(NumericVar *base, NumericVar *exp, NumericVar *result) static void power_var_int(NumericVar *base, int exp, NumericVar *result, int rscale) { + unsigned int mask; bool neg; NumericVar base_prod; int local_rscale; + /* Handle some common special cases, as well as corner cases */ switch (exp) { case 0: @@ -5733,23 +5735,43 @@ power_var_int(NumericVar *base, int exp, NumericVar *result, int rscale) * pattern of exp. We do the multiplications with some extra precision. */ neg = (exp < 0); - exp = Abs(exp); + mask = Abs(exp); local_rscale = rscale + MUL_GUARD_DIGITS * 2; init_var(&base_prod); set_var_from_var(base, &base_prod); - if (exp & 1) + if (mask & 1) set_var_from_var(base, result); else set_var_from_var(&const_one, result); - while ((exp >>= 1) > 0) + while ((mask >>= 1) > 0) { mul_var(&base_prod, &base_prod, &base_prod, local_rscale); - if (exp & 1) + if (mask & 1) mul_var(&base_prod, result, result, local_rscale); + + /* + * When abs(base) > 1, the number of digits to the left of the decimal + * point in base_prod doubles at each iteration, so if exp is large we + * could easily spend large amounts of time and memory space doing the + * multiplications. But once the weight exceeds what will fit in + * int16, the final result is guaranteed to overflow (or underflow, if + * exp < 0), so we can give up before wasting too many cycles. + */ + if (base_prod.weight > SHRT_MAX || result->weight > SHRT_MAX) + { + /* overflow, unless neg, in which case result should be 0 */ + if (!neg) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + zero_var(result); + neg = false; + break; + } } free_var(&base_prod); diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index 94cb0ed551d..5fafdaf13f5 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1390,3 +1390,22 @@ select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123; 12345678901234567890 (1 row) +-- +-- Test code path for raising to integer powers +-- +select 10.0 ^ -2147483648 as rounds_to_zero; + rounds_to_zero +-------------------- + 0.0000000000000000 +(1 row) + +select 10.0 ^ -2147483647 as rounds_to_zero; + rounds_to_zero +-------------------- + 0.0000000000000000 +(1 row) + +select 10.0 ^ 2147483647 as overflows; +ERROR: value overflows numeric format +select 117743296169.0 ^ 1000000000 as overflows; +ERROR: value overflows numeric format diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index b2dc46fae28..5c08717e7a9 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -828,3 +828,12 @@ select 12345678901234567890 % 123; select 12345678901234567890 / 123; select div(12345678901234567890, 123); select div(12345678901234567890, 123) * 123 + 12345678901234567890 % 123; + +-- +-- Test code path for raising to integer powers +-- + +select 10.0 ^ -2147483648 as rounds_to_zero; +select 10.0 ^ -2147483647 as rounds_to_zero; +select 10.0 ^ 2147483647 as overflows; +select 117743296169.0 ^ 1000000000 as overflows;