Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit0d6b874

Browse files
committed
Fix corner-case errors and loss of precision in numeric_power().
This fixes a couple of related problems that arise when raisingnumbers 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 onlycope if the exponent was small enough to go through power_var_int().Otherwise it would throw an internal error, attempting to take thelogarithm of a negative number. Fix this by adding suitable handlingto 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 absolutevalue is slightly less than 1 to a very large power, the result shouldapproach zero as the power is increased. However, in some cases, forsufficiently large powers, this would lose all precision and return 1instead of 0. This was due to the way that the local_rscale was beingcalculated for the final full-precision calculation: local_rscale = rscale + (int) val - ln_dweight + 8The first two terms on the right hand side are meant to give thenumber of significant digits required in the result ("val" being theestimated result weight). However, this failed to account for the factthat rscale is clipped to a maximum of NUMERIC_MAX_DISPLAY_SCALE(1000), and the result weight might be less then -1000, causing theirsum to be negative, leading to a loss of precision. Fix this byforcing 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 toensure 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 theresult is indistinguishable from zero. Some paths through this codealready returned zero in such cases, but others were throwing overflowerrors.Dean Rasheed, reviewed by Yugo Nagata.Discussion:http://postgr.es/m/CAEZATCW6Dvq7+3wN3tt5jLj-FyOcUgT5xNoOqce5=6Su0bCR0w@mail.gmail.com
1 parentf051b87 commit0d6b874

File tree

3 files changed

+132
-15
lines changed

3 files changed

+132
-15
lines changed

‎src/backend/utils/adt/numeric.c

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3932,7 +3932,9 @@ numeric_power(PG_FUNCTION_ARGS)
39323932
/*
39333933
* The SQL spec requires that we emit a particular SQLSTATE error code for
39343934
* certain error conditions. Specifically, we don't return a
3935-
* divide-by-zero error code for 0 ^ -1.
3935+
* divide-by-zero error code for 0 ^ -1. Raising a negative number to a
3936+
* non-integer power must produce the same error code, but that case is
3937+
* handled in power_var().
39363938
*/
39373939
sign1=numeric_sign_internal(num1);
39383940
sign2=numeric_sign_internal(num2);
@@ -3942,11 +3944,6 @@ numeric_power(PG_FUNCTION_ARGS)
39423944
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
39433945
errmsg("zero raised to a negative power is undefined")));
39443946

3945-
if (sign1<0&& !numeric_is_integral(num2))
3946-
ereport(ERROR,
3947-
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
3948-
errmsg("a negative number raised to a non-integer power yields a complex result")));
3949-
39503947
/*
39513948
* Initialize things
39523949
*/
@@ -9783,12 +9780,18 @@ exp_var(const NumericVar *arg, NumericVar *result, int rscale)
97839780
*/
97849781
val=numericvar_to_double_no_overflow(&x);
97859782

9786-
/* Guard against overflow */
9783+
/* Guard against overflow/underflow */
97879784
/* If you change this limit, see also power_var()'s limit */
97889785
if (Abs(val) >=NUMERIC_MAX_RESULT_SCALE*3)
9789-
ereport(ERROR,
9790-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
9791-
errmsg("value overflows numeric format")));
9786+
{
9787+
if (val>0)
9788+
ereport(ERROR,
9789+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
9790+
errmsg("value overflows numeric format")));
9791+
zero_var(result);
9792+
result->dscale=rscale;
9793+
return;
9794+
}
97929795

97939796
/* decimal weight = log10(e^x) = x * log10(e) */
97949797
dweight= (int) (val*0.434294481903252);
@@ -10146,10 +10149,13 @@ log_var(const NumericVar *base, const NumericVar *num, NumericVar *result)
1014610149
staticvoid
1014710150
power_var(constNumericVar*base,constNumericVar*exp,NumericVar*result)
1014810151
{
10152+
intres_sign;
10153+
NumericVarabs_base;
1014910154
NumericVarln_base;
1015010155
NumericVarln_num;
1015110156
intln_dweight;
1015210157
intrscale;
10158+
intsig_digits;
1015310159
intlocal_rscale;
1015410160
doubleval;
1015510161

@@ -10189,9 +10195,40 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1018910195
return;
1019010196
}
1019110197

10198+
init_var(&abs_base);
1019210199
init_var(&ln_base);
1019310200
init_var(&ln_num);
1019410201

10202+
/*
10203+
* If base is negative, insist that exp be an integer. The result is then
10204+
* positive if exp is even and negative if exp is odd.
10205+
*/
10206+
if (base->sign==NUMERIC_NEG)
10207+
{
10208+
/*
10209+
* Check that exp is an integer. This error code is defined by the
10210+
* SQL standard, and matches other errors in numeric_power().
10211+
*/
10212+
if (exp->ndigits>0&&exp->ndigits>exp->weight+1)
10213+
ereport(ERROR,
10214+
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
10215+
errmsg("a negative number raised to a non-integer power yields a complex result")));
10216+
10217+
/* Test if exp is odd or even */
10218+
if (exp->ndigits>0&&exp->ndigits==exp->weight+1&&
10219+
(exp->digits[exp->ndigits-1]&1))
10220+
res_sign=NUMERIC_NEG;
10221+
else
10222+
res_sign=NUMERIC_POS;
10223+
10224+
/* Then work with abs(base) below */
10225+
set_var_from_var(base,&abs_base);
10226+
abs_base.sign=NUMERIC_POS;
10227+
base=&abs_base;
10228+
}
10229+
else
10230+
res_sign=NUMERIC_POS;
10231+
1019510232
/*----------
1019610233
* Decide on the scale for the ln() calculation. For this we need an
1019710234
* estimate of the weight of the result, which we obtain by doing an
@@ -10222,11 +10259,17 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1022210259

1022310260
val=numericvar_to_double_no_overflow(&ln_num);
1022410261

10225-
/* initial overflow test with fuzz factor */
10262+
/* initial overflow/underflow test with fuzz factor */
1022610263
if (Abs(val)>NUMERIC_MAX_RESULT_SCALE*3.01)
10227-
ereport(ERROR,
10228-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
10229-
errmsg("value overflows numeric format")));
10264+
{
10265+
if (val>0)
10266+
ereport(ERROR,
10267+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
10268+
errmsg("value overflows numeric format")));
10269+
zero_var(result);
10270+
result->dscale=NUMERIC_MAX_DISPLAY_SCALE;
10271+
return;
10272+
}
1023010273

1023110274
val *=0.434294481903252;/* approximate decimal result weight */
1023210275

@@ -10237,8 +10280,12 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1023710280
rscale=Max(rscale,NUMERIC_MIN_DISPLAY_SCALE);
1023810281
rscale=Min(rscale,NUMERIC_MAX_DISPLAY_SCALE);
1023910282

10283+
/* significant digits required in the result */
10284+
sig_digits=rscale+ (int)val;
10285+
sig_digits=Max(sig_digits,0);
10286+
1024010287
/* set the scale for the real exp * ln(base) calculation */
10241-
local_rscale=rscale+ (int)val-ln_dweight+8;
10288+
local_rscale=sig_digits-ln_dweight+8;
1024210289
local_rscale=Max(local_rscale,NUMERIC_MIN_DISPLAY_SCALE);
1024310290

1024410291
/* and do the real calculation */
@@ -10249,8 +10296,12 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1024910296

1025010297
exp_var(&ln_num,result,rscale);
1025110298

10299+
if (res_sign==NUMERIC_NEG&&result->ndigits>0)
10300+
result->sign=NUMERIC_NEG;
10301+
1025210302
free_var(&ln_num);
1025310303
free_var(&ln_base);
10304+
free_var(&abs_base);
1025410305
}
1025510306

1025610307
/*

‎src/test/regress/expected/numeric.out

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2333,6 +2333,12 @@ select 1.000000000123 ^ (-2147483648);
23332333
0.7678656556403084
23342334
(1 row)
23352335

2336+
select 0.9999999999 ^ 23300000000000 = 0 as rounds_to_zero;
2337+
rounds_to_zero
2338+
----------------
2339+
t
2340+
(1 row)
2341+
23362342
-- cases that used to error out
23372343
select 0.12 ^ (-25);
23382344
?column?
@@ -2346,6 +2352,43 @@ select 0.5678 ^ (-85);
23462352
782333637740774446257.7719390061997396
23472353
(1 row)
23482354

2355+
select 0.9999999999 ^ 70000000000000 = 0 as underflows;
2356+
underflows
2357+
------------
2358+
t
2359+
(1 row)
2360+
2361+
-- negative base to integer powers
2362+
select (-1.0) ^ 2147483646;
2363+
?column?
2364+
--------------------
2365+
1.0000000000000000
2366+
(1 row)
2367+
2368+
select (-1.0) ^ 2147483647;
2369+
?column?
2370+
---------------------
2371+
-1.0000000000000000
2372+
(1 row)
2373+
2374+
select (-1.0) ^ 2147483648;
2375+
?column?
2376+
--------------------
2377+
1.0000000000000000
2378+
(1 row)
2379+
2380+
select (-1.0) ^ 1000000000000000;
2381+
?column?
2382+
--------------------
2383+
1.0000000000000000
2384+
(1 row)
2385+
2386+
select (-1.0) ^ 1000000000000001;
2387+
?column?
2388+
---------------------
2389+
-1.0000000000000000
2390+
(1 row)
2391+
23492392
--
23502393
-- Tests for raising to non-integer powers
23512394
--
@@ -2482,6 +2525,18 @@ select exp('-inf'::numeric);
24822525
0
24832526
(1 row)
24842527

2528+
select exp(-5000::numeric) = 0 as rounds_to_zero;
2529+
rounds_to_zero
2530+
----------------
2531+
t
2532+
(1 row)
2533+
2534+
select exp(-10000::numeric) = 0 as underflows;
2535+
underflows
2536+
------------
2537+
t
2538+
(1 row)
2539+
24852540
-- cases that used to generate inaccurate results
24862541
select exp(32.999);
24872542
exp

‎src/test/regress/sql/numeric.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,10 +1092,19 @@ select 3.789 ^ 35;
10921092
select1.2 ^345;
10931093
select0.12 ^ (-20);
10941094
select1.000000000123 ^ (-2147483648);
1095+
select0.9999999999 ^23300000000000=0as rounds_to_zero;
10951096

10961097
-- cases that used to error out
10971098
select0.12 ^ (-25);
10981099
select0.5678 ^ (-85);
1100+
select0.9999999999 ^70000000000000=0as underflows;
1101+
1102+
-- negative base to integer powers
1103+
select (-1.0) ^2147483646;
1104+
select (-1.0) ^2147483647;
1105+
select (-1.0) ^2147483648;
1106+
select (-1.0) ^1000000000000000;
1107+
select (-1.0) ^1000000000000001;
10991108

11001109
--
11011110
-- Tests for raising to non-integer powers
@@ -1138,6 +1147,8 @@ select exp(1.0::numeric(71,70));
11381147
select exp('nan'::numeric);
11391148
select exp('inf'::numeric);
11401149
select exp('-inf'::numeric);
1150+
select exp(-5000::numeric)=0as rounds_to_zero;
1151+
select exp(-10000::numeric)=0as underflows;
11411152

11421153
-- cases that used to generate inaccurate results
11431154
select exp(32.999);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp