]> granicus.if.org Git - postgresql/commitdiff
Fix power_var_int() for large integer exponents.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 12 Sep 2014 03:31:06 +0000 (23:31 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 12 Sep 2014 03:31:06 +0000 (23:31 -0400)
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.

src/backend/utils/adt/numeric.c
src/test/regress/expected/numeric.out
src/test/regress/sql/numeric.sql

index ca3b6ce7570de3844f32f117a3792d5097916dea..b7d8d3e0b0e490707d8a6c18769e62e4a4f03ce1 100644 (file)
@@ -5440,10 +5440,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:
@@ -5477,23 +5479,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);
index 0803e7bae62cba6400c314ba25dd11b89d193a26..635c7d98408b20b29e6f17461bca3e7d5a3e2501 100644 (file)
@@ -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
index f33a408f14066634fd998512dc052af77563fad1..1d380face20cb38bd9c55324ef39dc584b3bc1cd 100644 (file)
@@ -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;