aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/utils/adt/numeric.c35
-rw-r--r--src/test/regress/expected/numeric.out102
-rw-r--r--src/test/regress/sql/numeric.sql25
3 files changed, 153 insertions, 9 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 041d0dbc1d6..e222308ed60 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -221,6 +221,13 @@ struct NumericData
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
: ((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
* 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));
/*
- * 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 = Min(scale, NUMERIC_MAX_RESULT_SCALE);
+ scale = Max(scale, -(NUMERIC_WEIGHT_MAX + 1) * DEC_DIGITS - 1);
+ scale = Min(scale, NUMERIC_DSCALE_MAX);
/*
* 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));
/*
- * 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 = Min(scale, NUMERIC_MAX_RESULT_SCALE);
+ scale = Max(scale, -(NUMERIC_WEIGHT_MAX + 1) * DEC_DIGITS);
+ scale = Min(scale, NUMERIC_DSCALE_MAX);
/*
* 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
* 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 = 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
* certainly will overflow/underflow.
*/
- if (f > 3 * SHRT_MAX * DEC_DIGITS)
+ if (f > 3 * NUMERIC_WEIGHT_MAX * DEC_DIGITS)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
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
* 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 */
if (!neg)
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index a9cfa533e06..cecd29cea2a 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -824,6 +824,108 @@ FROM generate_series(-5,5) AS t(i);
5 | -300000 | -200000 | -100000 | 100000 | 200000 | 300000
(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
-- numeric and float8 versions of the function in this file.
-- errors
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 7d542d1ab84..aa3877a0172 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -699,6 +699,31 @@ SELECT i as pow,
round((2.5 * 10 ^ i)::numeric, -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
-- numeric and float8 versions of the function in this file.