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

Commit4dd5ce2

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 parent317632f commit4dd5ce2

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
@@ -3993,7 +3993,9 @@ numeric_power(PG_FUNCTION_ARGS)
39933993
/*
39943994
* The SQL spec requires that we emit a particular SQLSTATE error code for
39953995
* certain error conditions. Specifically, we don't return a
3996-
* divide-by-zero error code for 0 ^ -1.
3996+
* divide-by-zero error code for 0 ^ -1. Raising a negative number to a
3997+
* non-integer power must produce the same error code, but that case is
3998+
* handled in power_var().
39973999
*/
39984000
sign1=numeric_sign_internal(num1);
39994001
sign2=numeric_sign_internal(num2);
@@ -4003,11 +4005,6 @@ numeric_power(PG_FUNCTION_ARGS)
40034005
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
40044006
errmsg("zero raised to a negative power is undefined")));
40054007

4006-
if (sign1<0&& !numeric_is_integral(num2))
4007-
ereport(ERROR,
4008-
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
4009-
errmsg("a negative number raised to a non-integer power yields a complex result")));
4010-
40114008
/*
40124009
* Initialize things
40134010
*/
@@ -9822,12 +9819,18 @@ exp_var(const NumericVar *arg, NumericVar *result, int rscale)
98229819
*/
98239820
val=numericvar_to_double_no_overflow(&x);
98249821

9825-
/* Guard against overflow */
9822+
/* Guard against overflow/underflow */
98269823
/* If you change this limit, see also power_var()'s limit */
98279824
if (Abs(val) >=NUMERIC_MAX_RESULT_SCALE*3)
9828-
ereport(ERROR,
9829-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
9830-
errmsg("value overflows numeric format")));
9825+
{
9826+
if (val>0)
9827+
ereport(ERROR,
9828+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
9829+
errmsg("value overflows numeric format")));
9830+
zero_var(result);
9831+
result->dscale=rscale;
9832+
return;
9833+
}
98319834

98329835
/* decimal weight = log10(e^x) = x * log10(e) */
98339836
dweight= (int) (val*0.434294481903252);
@@ -10185,10 +10188,13 @@ log_var(const NumericVar *base, const NumericVar *num, NumericVar *result)
1018510188
staticvoid
1018610189
power_var(constNumericVar*base,constNumericVar*exp,NumericVar*result)
1018710190
{
10191+
intres_sign;
10192+
NumericVarabs_base;
1018810193
NumericVarln_base;
1018910194
NumericVarln_num;
1019010195
intln_dweight;
1019110196
intrscale;
10197+
intsig_digits;
1019210198
intlocal_rscale;
1019310199
doubleval;
1019410200

@@ -10228,9 +10234,40 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1022810234
return;
1022910235
}
1023010236

10237+
init_var(&abs_base);
1023110238
init_var(&ln_base);
1023210239
init_var(&ln_num);
1023310240

10241+
/*
10242+
* If base is negative, insist that exp be an integer. The result is then
10243+
* positive if exp is even and negative if exp is odd.
10244+
*/
10245+
if (base->sign==NUMERIC_NEG)
10246+
{
10247+
/*
10248+
* Check that exp is an integer. This error code is defined by the
10249+
* SQL standard, and matches other errors in numeric_power().
10250+
*/
10251+
if (exp->ndigits>0&&exp->ndigits>exp->weight+1)
10252+
ereport(ERROR,
10253+
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
10254+
errmsg("a negative number raised to a non-integer power yields a complex result")));
10255+
10256+
/* Test if exp is odd or even */
10257+
if (exp->ndigits>0&&exp->ndigits==exp->weight+1&&
10258+
(exp->digits[exp->ndigits-1]&1))
10259+
res_sign=NUMERIC_NEG;
10260+
else
10261+
res_sign=NUMERIC_POS;
10262+
10263+
/* Then work with abs(base) below */
10264+
set_var_from_var(base,&abs_base);
10265+
abs_base.sign=NUMERIC_POS;
10266+
base=&abs_base;
10267+
}
10268+
else
10269+
res_sign=NUMERIC_POS;
10270+
1023410271
/*----------
1023510272
* Decide on the scale for the ln() calculation. For this we need an
1023610273
* estimate of the weight of the result, which we obtain by doing an
@@ -10261,11 +10298,17 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1026110298

1026210299
val=numericvar_to_double_no_overflow(&ln_num);
1026310300

10264-
/* initial overflow test with fuzz factor */
10301+
/* initial overflow/underflow test with fuzz factor */
1026510302
if (Abs(val)>NUMERIC_MAX_RESULT_SCALE*3.01)
10266-
ereport(ERROR,
10267-
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
10268-
errmsg("value overflows numeric format")));
10303+
{
10304+
if (val>0)
10305+
ereport(ERROR,
10306+
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
10307+
errmsg("value overflows numeric format")));
10308+
zero_var(result);
10309+
result->dscale=NUMERIC_MAX_DISPLAY_SCALE;
10310+
return;
10311+
}
1026910312

1027010313
val *=0.434294481903252;/* approximate decimal result weight */
1027110314

@@ -10276,8 +10319,12 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1027610319
rscale=Max(rscale,NUMERIC_MIN_DISPLAY_SCALE);
1027710320
rscale=Min(rscale,NUMERIC_MAX_DISPLAY_SCALE);
1027810321

10322+
/* significant digits required in the result */
10323+
sig_digits=rscale+ (int)val;
10324+
sig_digits=Max(sig_digits,0);
10325+
1027910326
/* set the scale for the real exp * ln(base) calculation */
10280-
local_rscale=rscale+ (int)val-ln_dweight+8;
10327+
local_rscale=sig_digits-ln_dweight+8;
1028110328
local_rscale=Max(local_rscale,NUMERIC_MIN_DISPLAY_SCALE);
1028210329

1028310330
/* and do the real calculation */
@@ -10288,8 +10335,12 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
1028810335

1028910336
exp_var(&ln_num,result,rscale);
1029010337

10338+
if (res_sign==NUMERIC_NEG&&result->ndigits>0)
10339+
result->sign=NUMERIC_NEG;
10340+
1029110341
free_var(&ln_num);
1029210342
free_var(&ln_base);
10343+
free_var(&abs_base);
1029310344
}
1029410345

1029510346
/*

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,6 +2396,12 @@ select 1.000000000123 ^ (-2147483648);
23962396
0.7678656556403084
23972397
(1 row)
23982398

2399+
select 0.9999999999 ^ 23300000000000 = 0 as rounds_to_zero;
2400+
rounds_to_zero
2401+
----------------
2402+
t
2403+
(1 row)
2404+
23992405
-- cases that used to error out
24002406
select 0.12 ^ (-25);
24012407
?column?
@@ -2409,6 +2415,43 @@ select 0.5678 ^ (-85);
24092415
782333637740774446257.7719390061997396
24102416
(1 row)
24112417

2418+
select 0.9999999999 ^ 70000000000000 = 0 as underflows;
2419+
underflows
2420+
------------
2421+
t
2422+
(1 row)
2423+
2424+
-- negative base to integer powers
2425+
select (-1.0) ^ 2147483646;
2426+
?column?
2427+
--------------------
2428+
1.0000000000000000
2429+
(1 row)
2430+
2431+
select (-1.0) ^ 2147483647;
2432+
?column?
2433+
---------------------
2434+
-1.0000000000000000
2435+
(1 row)
2436+
2437+
select (-1.0) ^ 2147483648;
2438+
?column?
2439+
--------------------
2440+
1.0000000000000000
2441+
(1 row)
2442+
2443+
select (-1.0) ^ 1000000000000000;
2444+
?column?
2445+
--------------------
2446+
1.0000000000000000
2447+
(1 row)
2448+
2449+
select (-1.0) ^ 1000000000000001;
2450+
?column?
2451+
---------------------
2452+
-1.0000000000000000
2453+
(1 row)
2454+
24122455
--
24132456
-- Tests for raising to non-integer powers
24142457
--
@@ -2545,6 +2588,18 @@ select exp('-inf'::numeric);
25452588
0
25462589
(1 row)
25472590

2591+
select exp(-5000::numeric) = 0 as rounds_to_zero;
2592+
rounds_to_zero
2593+
----------------
2594+
t
2595+
(1 row)
2596+
2597+
select exp(-10000::numeric) = 0 as underflows;
2598+
underflows
2599+
------------
2600+
t
2601+
(1 row)
2602+
25482603
-- cases that used to generate inaccurate results
25492604
select exp(32.999);
25502605
exp

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,10 +1126,19 @@ select 3.789 ^ 35;
11261126
select1.2 ^345;
11271127
select0.12 ^ (-20);
11281128
select1.000000000123 ^ (-2147483648);
1129+
select0.9999999999 ^23300000000000=0as rounds_to_zero;
11291130

11301131
-- cases that used to error out
11311132
select0.12 ^ (-25);
11321133
select0.5678 ^ (-85);
1134+
select0.9999999999 ^70000000000000=0as underflows;
1135+
1136+
-- negative base to integer powers
1137+
select (-1.0) ^2147483646;
1138+
select (-1.0) ^2147483647;
1139+
select (-1.0) ^2147483648;
1140+
select (-1.0) ^1000000000000000;
1141+
select (-1.0) ^1000000000000001;
11331142

11341143
--
11351144
-- Tests for raising to non-integer powers
@@ -1172,6 +1181,8 @@ select exp(1.0::numeric(71,70));
11721181
select exp('nan'::numeric);
11731182
select exp('inf'::numeric);
11741183
select exp('-inf'::numeric);
1184+
select exp(-5000::numeric)=0as rounds_to_zero;
1185+
select exp(-10000::numeric)=0as underflows;
11751186

11761187
-- cases that used to generate inaccurate results
11771188
select exp(32.999);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp