summaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
authorDean Rasheed <dean.a.rasheed@gmail.com>2016-05-05 11:16:17 +0100
committerDean Rasheed <dean.a.rasheed@gmail.com>2016-05-05 11:16:17 +0100
commit18a02ad2a506e4425c6dd2ea235039cd5659467d (patch)
treec8b54ce49b91a13ecaba32609c76a5d2c39334b6 /src/backend/utils
parentc1543a81a7a89207b6c45b8f3f7f07b1148fcc6e (diff)
downloadpostgresql-18a02ad2a506e4425c6dd2ea235039cd5659467d.tar.gz
Fix corner-case loss of precision in numeric pow() calculation
Commit 7d9a4737c268f61fb8800957631f12d3f13be218 greatly improved the accuracy of the numeric transcendental functions, however it failed to consider the case where the result from pow() is close to the overflow threshold, for example 0.12 ^ -2345.6. For such inputs, where the result has more than 2000 digits before the decimal point, the decimal result weight estimate was being clamped to 2000, leading to a loss of precision in the final calculation. Fix this by replacing the clamping code with an overflow test that aborts the calculation early if the final result is sure to overflow, based on the overflow limit in exp_var(). This provides the same protection against integer overflow in the subsequent result scale computation as the original clamping code, but it also ensures that precision is never lost and saves compute cycles in cases that are sure to overflow. The new early overflow test works with the initial low-precision result (expected to be accurate to around 8 significant digits) and includes a small fuzz factor to ensure that it doesn't kick in for values that would not overflow exp_var(), so the overall overflow threshold of pow() is unchanged and consistent for all inputs with non-integer exponents. Author: Dean Rasheed Reviewed-by: Tom Lane Discussion: http://www.postgresql.org/message-id/CAEZATCUj3U-cQj0jjoia=qgs0SjE3auroxh8swvNKvZWUqegrg@mail.gmail.com See-also: http://www.postgresql.org/message-id/CAEZATCV7w+8iB=07dJ8Q0zihXQT1semcQuTeK+4_rogC_zq5Hw@mail.gmail.com
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/adt/numeric.c20
1 files changed, 16 insertions, 4 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 3ba373a15b..3d21e33a26 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -7591,6 +7591,7 @@ exp_var(NumericVar *arg, NumericVar *result, int rscale)
val = numericvar_to_double_no_overflow(&x);
/* Guard against overflow */
+ /* If you change this limit, see also power_var()'s limit */
if (Abs(val) >= NUMERIC_MAX_RESULT_SCALE * 3)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
@@ -7992,6 +7993,15 @@ power_var(NumericVar *base, NumericVar *exp, NumericVar *result)
*
* We want result = e ^ (exp * ln(base))
* so result dweight = log10(result) = exp * ln(base) * log10(e)
+ *
+ * We also perform a crude overflow test here so that we can exit early if
+ * the full-precision result is sure to overflow, and to guard against
+ * integer overflow when determining the scale for the real calculation.
+ * exp_var() supports inputs up to NUMERIC_MAX_RESULT_SCALE * 3, so the
+ * result will overflow if exp * ln(base) >= NUMERIC_MAX_RESULT_SCALE * 3.
+ * Since the values here are only approximations, we apply a small fuzz
+ * factor to this overflow test and let exp_var() determine the exact
+ * overflow threshold so that it is consistent for all inputs.
*----------
*/
ln_dweight = estimate_ln_dweight(base);
@@ -8006,11 +8016,13 @@ power_var(NumericVar *base, NumericVar *exp, NumericVar *result)
val = numericvar_to_double_no_overflow(&ln_num);
- val *= 0.434294481903252; /* approximate decimal result weight */
+ /* initial overflow test with fuzz factor */
+ if (Abs(val) > NUMERIC_MAX_RESULT_SCALE * 3.01)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value overflows numeric format")));
- /* limit to something that won't cause integer overflow */
- val = Max(val, -NUMERIC_MAX_RESULT_SCALE);
- val = Min(val, NUMERIC_MAX_RESULT_SCALE);
+ val *= 0.434294481903252; /* approximate decimal result weight */
/* choose the result scale */
rscale = NUMERIC_MIN_SIG_DIGITS - (int) val;