Numeric x^y is supported for x < 0 if y is an integer, but this currently fails if y is outside the range of an int32:
SELECT (-1.0) ^ 2147483647; ?column? --------------------- -1.0000000000000000 (1 row) SELECT (-1.0) ^ 2147483648; ERROR: cannot take logarithm of a negative number because only the power_var_int() code path in power_var() handles negative bases correctly. Attached is a patch to fix that. Regards, Dean
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c new file mode 100644 index eb78f0b..3eb0d90 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -3934,11 +3934,6 @@ numeric_power(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), errmsg("zero raised to a negative power is undefined"))); - if (sign1 < 0 && !numeric_is_integral(num2)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), - errmsg("a negative number raised to a non-integer power yields a complex result"))); - /* * Initialize things */ @@ -10138,6 +10133,8 @@ log_var(const NumericVar *base, const Nu static void power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result) { + int res_sign; + NumericVar abs_base; NumericVar ln_base; NumericVar ln_num; int ln_dweight; @@ -10181,9 +10178,37 @@ power_var(const NumericVar *base, const return; } + init_var(&abs_base); init_var(&ln_base); init_var(&ln_num); + /* + * If base is negative, insist that exp be an integer. The result is then + * positive if exp is even and negative if exp is odd. + */ + if (base->sign == NUMERIC_NEG) + { + /* digits after the decimal point? */ + if (exp->ndigits > 0 && exp->ndigits > exp->weight + 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION), + errmsg("a negative number raised to a non-integer power yields a complex result"))); + + /* odd exponent? */ + if (exp->ndigits > 0 && exp->ndigits == exp->weight + 1 && + (exp->digits[exp->ndigits - 1] & 1)) + res_sign = NUMERIC_NEG; + else + res_sign = NUMERIC_POS; + + /* work with abs(base) */ + set_var_from_var(base, &abs_base); + abs_base.sign = NUMERIC_POS; + base = &abs_base; + } + else + res_sign = NUMERIC_POS; + /*---------- * Decide on the scale for the ln() calculation. For this we need an * estimate of the weight of the result, which we obtain by doing an @@ -10240,9 +10265,12 @@ power_var(const NumericVar *base, const mul_var(&ln_base, exp, &ln_num, local_rscale); exp_var(&ln_num, result, rscale); + if (res_sign == NUMERIC_NEG && result->ndigits > 0) + result->sign = NUMERIC_NEG; free_var(&ln_num); free_var(&ln_base); + free_var(&abs_base); } /* diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out new file mode 100644 index 30a5642..94bc773 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -2340,6 +2340,37 @@ select 0.5678 ^ (-85); 782333637740774446257.7719390061997396 (1 row) +-- negative base to integer powers +select (-1.0) ^ 2147483646; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select (-1.0) ^ 2147483647; + ?column? +--------------------- + -1.0000000000000000 +(1 row) + +select (-1.0) ^ 2147483648; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select (-1.0) ^ 1000000000000000; + ?column? +-------------------- + 1.0000000000000000 +(1 row) + +select (-1.0) ^ 1000000000000001; + ?column? +--------------------- + -1.0000000000000000 +(1 row) + -- -- Tests for raising to non-integer powers -- diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql new file mode 100644 index db812c8..58c1c69 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -1095,6 +1095,13 @@ select 1.000000000123 ^ (-2147483648); select 0.12 ^ (-25); select 0.5678 ^ (-85); +-- negative base to integer powers +select (-1.0) ^ 2147483646; +select (-1.0) ^ 2147483647; +select (-1.0) ^ 2147483648; +select (-1.0) ^ 1000000000000000; +select (-1.0) ^ 1000000000000001; + -- -- Tests for raising to non-integer powers --