diff options
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 35 |
1 files changed, 26 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) |