| ((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
PG_RETURN_NUMERIC(duplicate_numeric(num));
/*
- * 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
PG_RETURN_NUMERIC(duplicate_numeric(num));
/*
- * 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
add_var(dest, &tmp_var, dest);
/* Result will overflow if weight overflows int16 */
- if (dest->weight > SHRT_MAX)
+ if (dest->weight > NUMERIC_WEIGHT_MAX)
goto out_of_range;
/* Begin a new group */
add_var(dest, &tmp_var, dest);
/* Result will overflow if weight overflows int16 */
- if (dest->weight > SHRT_MAX)
+ if (dest->weight > NUMERIC_WEIGHT_MAX)
goto out_of_range;
/* Begin a new group */
add_var(dest, &tmp_var, dest);
/* Result will overflow if weight overflows int16 */
- if (dest->weight > SHRT_MAX)
+ if (dest->weight > NUMERIC_WEIGHT_MAX)
goto out_of_range;
/* Begin a new group */
int64_to_numericvar(tmp, &tmp_var);
add_var(dest, &tmp_var, dest);
- if (dest->weight > SHRT_MAX)
+ if (dest->weight > NUMERIC_WEIGHT_MAX)
goto out_of_range;
dest->sign = sign;
/*
* 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);
f = 0; /* result is 0 or 1 (weight 0), or error */
/* overflow/underflow tests with fuzz factors */
- if (f > (SHRT_MAX + 1) * DEC_DIGITS)
+ if (f > (NUMERIC_WEIGHT_MAX + 1) * DEC_DIGITS)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
* 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)
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
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.