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

Commitece2969

Browse files
committed
Fix scale clamping in numeric round() and trunc().
The numeric round() and trunc() functions clamp the scale argument tothe range between +/- NUMERIC_MAX_RESULT_SCALE (2000), which is muchsmaller than the actual allowed range of type numeric. As a result,they return incorrect results when asked to round/truncate more than2000 digits before or after the decimal point.Fix by using the correct upper and lower scale limits based on theactual allowed (and documented) range of type numeric.While at it, use the new NUMERIC_WEIGHT_MAX constant instead ofSHRT_MAX in all other overflow checks, and fix a comment thinko inpower_var() introduced bye54a758 -- the minimum value ofln_dweight is -NUMERIC_DSCALE_MAX (-16383), not -SHRT_MAX, though thisdoesn't affect the point being made in the comment, that the resultinglocal_rscale value may exceed NUMERIC_MAX_DISPLAY_SCALE (1000).Back-patch to all supported branches.Dean Rasheed, reviewed by Joel Jacobson.Discussion:https://postgr.es/m/CAEZATCXB%2BrDTuMjhK5ZxcouufigSc-X4tGJCBTMpZ3n%3DxxQuhg%40mail.gmail.com
1 parent440aedc commitece2969

File tree

3 files changed

+153
-9
lines changed

3 files changed

+153
-9
lines changed

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

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ struct NumericData
221221
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
222222
: ((n)->choice.n_long.n_weight))
223223

224+
/*
225+
* Maximum weight of a stored Numeric value (based on the use of int16 for the
226+
* weight in NumericLong). Note that intermediate values held in NumericVar
227+
* and NumericSumAccum variables may have much larger weights.
228+
*/
229+
#defineNUMERIC_WEIGHT_MAXPG_INT16_MAX
230+
224231
/* ----------
225232
* NumericVar is the format we use for arithmetic. The digit-array part
226233
* is the same as the NumericData storage format, but the header is more
@@ -1230,10 +1237,15 @@ numeric_round(PG_FUNCTION_ARGS)
12301237
PG_RETURN_NUMERIC(make_result(&const_nan));
12311238

12321239
/*
1233-
* Limit the scale value to avoid possible overflow in calculations
1240+
* Limit the scale value to avoid possible overflow in calculations.
1241+
*
1242+
* These limits are based on the maximum number of digits a Numeric value
1243+
* can have before and after the decimal point, but we must allow for one
1244+
* extra digit before the decimal point, in case the most significant
1245+
* digit rounds up; we must check if that causes Numeric overflow.
12341246
*/
1235-
scale=Max(scale,-NUMERIC_MAX_RESULT_SCALE);
1236-
scale=Min(scale,NUMERIC_MAX_RESULT_SCALE);
1247+
scale=Max(scale,-(NUMERIC_WEIGHT_MAX+1)*DEC_DIGITS-1);
1248+
scale=Min(scale,NUMERIC_DSCALE_MAX);
12371249

12381250
/*
12391251
* Unpack the argument and round it at the proper digit position
@@ -1279,10 +1291,13 @@ numeric_trunc(PG_FUNCTION_ARGS)
12791291
PG_RETURN_NUMERIC(make_result(&const_nan));
12801292

12811293
/*
1282-
* Limit the scale value to avoid possible overflow in calculations
1294+
* Limit the scale value to avoid possible overflow in calculations.
1295+
*
1296+
* These limits are based on the maximum number of digits a Numeric value
1297+
* can have before and after the decimal point.
12831298
*/
1284-
scale=Max(scale,-NUMERIC_MAX_RESULT_SCALE);
1285-
scale=Min(scale,NUMERIC_MAX_RESULT_SCALE);
1299+
scale=Max(scale,-(NUMERIC_WEIGHT_MAX+1)*DEC_DIGITS);
1300+
scale=Min(scale,NUMERIC_DSCALE_MAX);
12861301

12871302
/*
12881303
* Unpack the argument and truncate it at the proper digit position
@@ -9256,7 +9271,8 @@ power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
92569271
/*
92579272
* Set the scale for the low-precision calculation, computing ln(base) to
92589273
* around 8 significant digits. Note that ln_dweight may be as small as
9259-
* -SHRT_MAX, so the scale may exceed NUMERIC_MAX_DISPLAY_SCALE here.
9274+
* -NUMERIC_DSCALE_MAX, so the scale may exceed NUMERIC_MAX_DISPLAY_SCALE
9275+
* here.
92609276
*/
92619277
local_rscale=8-ln_dweight;
92629278
local_rscale=Max(local_rscale,NUMERIC_MIN_DISPLAY_SCALE);
@@ -9396,7 +9412,7 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
93969412
* Apply crude overflow/underflow tests so we can exit early if the result
93979413
* certainly will overflow/underflow.
93989414
*/
9399-
if (f>3*SHRT_MAX*DEC_DIGITS)
9415+
if (f>3*NUMERIC_WEIGHT_MAX*DEC_DIGITS)
94009416
ereport(ERROR,
94019417
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
94029418
errmsg("value overflows numeric format")));
@@ -9466,7 +9482,8 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
94669482
* int16, the final result is guaranteed to overflow (or underflow, if
94679483
* exp < 0), so we can give up before wasting too many cycles.
94689484
*/
9469-
if (base_prod.weight>SHRT_MAX||result->weight>SHRT_MAX)
9485+
if (base_prod.weight>NUMERIC_WEIGHT_MAX||
9486+
result->weight>NUMERIC_WEIGHT_MAX)
94709487
{
94719488
/* overflow, unless neg, in which case result should be 0 */
94729489
if (!neg)

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

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,108 @@ FROM generate_series(-5,5) AS t(i);
824824
5 | -300000 | -200000 | -100000 | 100000 | 200000 | 300000
825825
(11 rows)
826826

827+
-- Check limits of rounding before the decimal point
828+
SELECT round(4.4e131071, -131071) = 4e131071;
829+
?column?
830+
----------
831+
t
832+
(1 row)
833+
834+
SELECT round(4.5e131071, -131071) = 5e131071;
835+
?column?
836+
----------
837+
t
838+
(1 row)
839+
840+
SELECT round(4.5e131071, -131072); -- loses all digits
841+
round
842+
-------
843+
0
844+
(1 row)
845+
846+
SELECT round(5.5e131071, -131072); -- rounds up and overflows
847+
ERROR: value overflows numeric format
848+
SELECT round(5.5e131071, -131073); -- loses all digits
849+
round
850+
-------
851+
0
852+
(1 row)
853+
854+
SELECT round(5.5e131071, -1000000); -- loses all digits
855+
round
856+
-------
857+
0
858+
(1 row)
859+
860+
-- Check limits of rounding after the decimal point
861+
SELECT round(5e-16383, 1000000) = 5e-16383;
862+
?column?
863+
----------
864+
t
865+
(1 row)
866+
867+
SELECT round(5e-16383, 16383) = 5e-16383;
868+
?column?
869+
----------
870+
t
871+
(1 row)
872+
873+
SELECT round(5e-16383, 16382) = 1e-16382;
874+
?column?
875+
----------
876+
t
877+
(1 row)
878+
879+
SELECT round(5e-16383, 16381) = 0;
880+
?column?
881+
----------
882+
t
883+
(1 row)
884+
885+
-- Check limits of trunc() before the decimal point
886+
SELECT trunc(9.9e131071, -131071) = 9e131071;
887+
?column?
888+
----------
889+
t
890+
(1 row)
891+
892+
SELECT trunc(9.9e131071, -131072); -- loses all digits
893+
trunc
894+
-------
895+
0
896+
(1 row)
897+
898+
SELECT trunc(9.9e131071, -131073); -- loses all digits
899+
trunc
900+
-------
901+
0
902+
(1 row)
903+
904+
SELECT trunc(9.9e131071, -1000000); -- loses all digits
905+
trunc
906+
-------
907+
0
908+
(1 row)
909+
910+
-- Check limits of trunc() after the decimal point
911+
SELECT trunc(5e-16383, 1000000) = 5e-16383;
912+
?column?
913+
----------
914+
t
915+
(1 row)
916+
917+
SELECT trunc(5e-16383, 16383) = 5e-16383;
918+
?column?
919+
----------
920+
t
921+
(1 row)
922+
923+
SELECT trunc(5e-16383, 16382) = 0;
924+
?column?
925+
----------
926+
t
927+
(1 row)
928+
827929
-- Testing for width_bucket(). For convenience, we test both the
828930
-- numeric and float8 versions of the function in this file.
829931
-- errors

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,31 @@ SELECT i as pow,
699699
round((2.5*10 ^ i)::numeric,-i)
700700
FROM generate_series(-5,5)AS t(i);
701701

702+
-- Check limits of rounding before the decimal point
703+
SELECT round(4.4e131071,-131071)= 4e131071;
704+
SELECT round(4.5e131071,-131071)= 5e131071;
705+
SELECT round(4.5e131071,-131072);-- loses all digits
706+
SELECT round(5.5e131071,-131072);-- rounds up and overflows
707+
SELECT round(5.5e131071,-131073);-- loses all digits
708+
SELECT round(5.5e131071,-1000000);-- loses all digits
709+
710+
-- Check limits of rounding after the decimal point
711+
SELECT round(5e-16383,1000000)= 5e-16383;
712+
SELECT round(5e-16383,16383)= 5e-16383;
713+
SELECT round(5e-16383,16382)= 1e-16382;
714+
SELECT round(5e-16383,16381)=0;
715+
716+
-- Check limits of trunc() before the decimal point
717+
SELECT trunc(9.9e131071,-131071)= 9e131071;
718+
SELECT trunc(9.9e131071,-131072);-- loses all digits
719+
SELECT trunc(9.9e131071,-131073);-- loses all digits
720+
SELECT trunc(9.9e131071,-1000000);-- loses all digits
721+
722+
-- Check limits of trunc() after the decimal point
723+
SELECT trunc(5e-16383,1000000)= 5e-16383;
724+
SELECT trunc(5e-16383,16383)= 5e-16383;
725+
SELECT trunc(5e-16383,16382)=0;
726+
702727
-- Testing for width_bucket(). For convenience, we test both the
703728
-- numeric and float8 versions of the function in this file.
704729

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp