mirror of
https://github.com/postgres/postgres.git
synced 2025-05-17 06:41:24 +03:00
Fix scale clamping in numeric round() and trunc().
The numeric round() and trunc() functions clamp the scale argument to the range between +/- NUMERIC_MAX_RESULT_SCALE (2000), which is much smaller than the actual allowed range of type numeric. As a result, they return incorrect results when asked to round/truncate more than 2000 digits before or after the decimal point. Fix by using the correct upper and lower scale limits based on the actual allowed (and documented) range of type numeric. While at it, use the new NUMERIC_WEIGHT_MAX constant instead of SHRT_MAX in all other overflow checks, and fix a comment thinko in power_var() introduced by e54a758d24 -- the minimum value of ln_dweight is -NUMERIC_DSCALE_MAX (-16383), not -SHRT_MAX, though this doesn't affect the point being made in the comment, that the resulting local_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
This commit is contained in:
parent
440aedc0fb
commit
ece2969266
@ -221,6 +221,13 @@ struct NumericData
|
|||||||
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
|
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
|
||||||
: ((n)->choice.n_long.n_weight))
|
: ((n)->choice.n_long.n_weight))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Maximum weight of a stored Numeric value (based on the use of int16 for the
|
||||||
|
* weight in NumericLong). Note that intermediate values held in NumericVar
|
||||||
|
* and NumericSumAccum variables may have much larger weights.
|
||||||
|
*/
|
||||||
|
#define NUMERIC_WEIGHT_MAX PG_INT16_MAX
|
||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* NumericVar is the format we use for arithmetic. The digit-array part
|
* NumericVar is the format we use for arithmetic. The digit-array part
|
||||||
* is the same as the NumericData storage format, but the header is more
|
* is the same as the NumericData storage format, but the header is more
|
||||||
@ -1230,10 +1237,15 @@ numeric_round(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_NUMERIC(make_result(&const_nan));
|
PG_RETURN_NUMERIC(make_result(&const_nan));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Limit the scale value to avoid possible overflow in calculations
|
* Limit the scale value to avoid possible overflow in calculations.
|
||||||
|
*
|
||||||
|
* These limits are based on the maximum number of digits a Numeric value
|
||||||
|
* can have before and after the decimal point, but we must allow for one
|
||||||
|
* extra digit before the decimal point, in case the most significant
|
||||||
|
* digit rounds up; we must check if that causes Numeric overflow.
|
||||||
*/
|
*/
|
||||||
scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE);
|
scale = Max(scale, -(NUMERIC_WEIGHT_MAX + 1) * DEC_DIGITS - 1);
|
||||||
scale = Min(scale, NUMERIC_MAX_RESULT_SCALE);
|
scale = Min(scale, NUMERIC_DSCALE_MAX);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unpack the argument and round it at the proper digit position
|
* Unpack the argument and round it at the proper digit position
|
||||||
@ -1279,10 +1291,13 @@ numeric_trunc(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_NUMERIC(make_result(&const_nan));
|
PG_RETURN_NUMERIC(make_result(&const_nan));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Limit the scale value to avoid possible overflow in calculations
|
* Limit the scale value to avoid possible overflow in calculations.
|
||||||
|
*
|
||||||
|
* These limits are based on the maximum number of digits a Numeric value
|
||||||
|
* can have before and after the decimal point.
|
||||||
*/
|
*/
|
||||||
scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE);
|
scale = Max(scale, -(NUMERIC_WEIGHT_MAX + 1) * DEC_DIGITS);
|
||||||
scale = Min(scale, NUMERIC_MAX_RESULT_SCALE);
|
scale = Min(scale, NUMERIC_DSCALE_MAX);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unpack the argument and truncate it at the proper digit position
|
* 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)
|
|||||||
/*
|
/*
|
||||||
* Set the scale for the low-precision calculation, computing ln(base) to
|
* Set the scale for the low-precision calculation, computing ln(base) to
|
||||||
* around 8 significant digits. Note that ln_dweight may be as small as
|
* around 8 significant digits. Note that ln_dweight may be as small as
|
||||||
* -SHRT_MAX, so the scale may exceed NUMERIC_MAX_DISPLAY_SCALE here.
|
* -NUMERIC_DSCALE_MAX, so the scale may exceed NUMERIC_MAX_DISPLAY_SCALE
|
||||||
|
* here.
|
||||||
*/
|
*/
|
||||||
local_rscale = 8 - ln_dweight;
|
local_rscale = 8 - ln_dweight;
|
||||||
local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE);
|
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)
|
|||||||
* Apply crude overflow/underflow tests so we can exit early if the result
|
* Apply crude overflow/underflow tests so we can exit early if the result
|
||||||
* certainly will overflow/underflow.
|
* certainly will overflow/underflow.
|
||||||
*/
|
*/
|
||||||
if (f > 3 * SHRT_MAX * DEC_DIGITS)
|
if (f > 3 * NUMERIC_WEIGHT_MAX * DEC_DIGITS)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||||
errmsg("value overflows numeric format")));
|
errmsg("value overflows numeric format")));
|
||||||
@ -9466,7 +9482,8 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
|
|||||||
* int16, the final result is guaranteed to overflow (or underflow, if
|
* int16, the final result is guaranteed to overflow (or underflow, if
|
||||||
* exp < 0), so we can give up before wasting too many cycles.
|
* exp < 0), so we can give up before wasting too many cycles.
|
||||||
*/
|
*/
|
||||||
if (base_prod.weight > SHRT_MAX || result->weight > SHRT_MAX)
|
if (base_prod.weight > NUMERIC_WEIGHT_MAX ||
|
||||||
|
result->weight > NUMERIC_WEIGHT_MAX)
|
||||||
{
|
{
|
||||||
/* overflow, unless neg, in which case result should be 0 */
|
/* overflow, unless neg, in which case result should be 0 */
|
||||||
if (!neg)
|
if (!neg)
|
||||||
|
@ -824,6 +824,108 @@ FROM generate_series(-5,5) AS t(i);
|
|||||||
5 | -300000 | -200000 | -100000 | 100000 | 200000 | 300000
|
5 | -300000 | -200000 | -100000 | 100000 | 200000 | 300000
|
||||||
(11 rows)
|
(11 rows)
|
||||||
|
|
||||||
|
-- Check limits of rounding before the decimal point
|
||||||
|
SELECT round(4.4e131071, -131071) = 4e131071;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT round(4.5e131071, -131071) = 5e131071;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT round(4.5e131071, -131072); -- loses all digits
|
||||||
|
round
|
||||||
|
-------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT round(5.5e131071, -131072); -- rounds up and overflows
|
||||||
|
ERROR: value overflows numeric format
|
||||||
|
SELECT round(5.5e131071, -131073); -- loses all digits
|
||||||
|
round
|
||||||
|
-------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT round(5.5e131071, -1000000); -- loses all digits
|
||||||
|
round
|
||||||
|
-------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Check limits of rounding after the decimal point
|
||||||
|
SELECT round(5e-16383, 1000000) = 5e-16383;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT round(5e-16383, 16383) = 5e-16383;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT round(5e-16383, 16382) = 1e-16382;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT round(5e-16383, 16381) = 0;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Check limits of trunc() before the decimal point
|
||||||
|
SELECT trunc(9.9e131071, -131071) = 9e131071;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT trunc(9.9e131071, -131072); -- loses all digits
|
||||||
|
trunc
|
||||||
|
-------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT trunc(9.9e131071, -131073); -- loses all digits
|
||||||
|
trunc
|
||||||
|
-------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT trunc(9.9e131071, -1000000); -- loses all digits
|
||||||
|
trunc
|
||||||
|
-------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- Check limits of trunc() after the decimal point
|
||||||
|
SELECT trunc(5e-16383, 1000000) = 5e-16383;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT trunc(5e-16383, 16383) = 5e-16383;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT trunc(5e-16383, 16382) = 0;
|
||||||
|
?column?
|
||||||
|
----------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- Testing for width_bucket(). For convenience, we test both the
|
-- Testing for width_bucket(). For convenience, we test both the
|
||||||
-- numeric and float8 versions of the function in this file.
|
-- numeric and float8 versions of the function in this file.
|
||||||
-- errors
|
-- errors
|
||||||
|
@ -699,6 +699,31 @@ SELECT i as pow,
|
|||||||
round((2.5 * 10 ^ i)::numeric, -i)
|
round((2.5 * 10 ^ i)::numeric, -i)
|
||||||
FROM generate_series(-5,5) AS t(i);
|
FROM generate_series(-5,5) AS t(i);
|
||||||
|
|
||||||
|
-- Check limits of rounding before the decimal point
|
||||||
|
SELECT round(4.4e131071, -131071) = 4e131071;
|
||||||
|
SELECT round(4.5e131071, -131071) = 5e131071;
|
||||||
|
SELECT round(4.5e131071, -131072); -- loses all digits
|
||||||
|
SELECT round(5.5e131071, -131072); -- rounds up and overflows
|
||||||
|
SELECT round(5.5e131071, -131073); -- loses all digits
|
||||||
|
SELECT round(5.5e131071, -1000000); -- loses all digits
|
||||||
|
|
||||||
|
-- Check limits of rounding after the decimal point
|
||||||
|
SELECT round(5e-16383, 1000000) = 5e-16383;
|
||||||
|
SELECT round(5e-16383, 16383) = 5e-16383;
|
||||||
|
SELECT round(5e-16383, 16382) = 1e-16382;
|
||||||
|
SELECT round(5e-16383, 16381) = 0;
|
||||||
|
|
||||||
|
-- Check limits of trunc() before the decimal point
|
||||||
|
SELECT trunc(9.9e131071, -131071) = 9e131071;
|
||||||
|
SELECT trunc(9.9e131071, -131072); -- loses all digits
|
||||||
|
SELECT trunc(9.9e131071, -131073); -- loses all digits
|
||||||
|
SELECT trunc(9.9e131071, -1000000); -- loses all digits
|
||||||
|
|
||||||
|
-- Check limits of trunc() after the decimal point
|
||||||
|
SELECT trunc(5e-16383, 1000000) = 5e-16383;
|
||||||
|
SELECT trunc(5e-16383, 16383) = 5e-16383;
|
||||||
|
SELECT trunc(5e-16383, 16382) = 0;
|
||||||
|
|
||||||
-- Testing for width_bucket(). For convenience, we test both the
|
-- Testing for width_bucket(). For convenience, we test both the
|
||||||
-- numeric and float8 versions of the function in this file.
|
-- numeric and float8 versions of the function in this file.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user