From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Sat, 10 Jul 2021 11:50:05 +0000 (+0100)
Subject: Fix numeric_mul() overflow due to too many digits after decimal point.
X-Git-Tag: REL_10_18~38
X-Git-Url: http://git.postgresql.org/gitweb/?a=commitdiff_plain;h=54a0ec1bd6d5f46938b8da7bbcf7c3cb26b8638d;p=postgresql.git

Fix numeric_mul() overflow due to too many digits after decimal point.

This fixes an overflow error when using the numeric * operator if the
result has more than 16383 digits after the decimal point by rounding
the result. Overflow errors should only occur if the result has too
many digits *before* the decimal point.

Discussion: https://postgr.es/m/CAEZATCUmeFWCrq2dNzZpRj5+6LfN85jYiDoqm+ucSXhb9U2TbA@mail.gmail.com
---

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index f51820c5186..3bc8ff0ebfe 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -202,6 +202,7 @@ struct NumericData
  */
 
 #define NUMERIC_DSCALE_MASK			0x3FFF
+#define NUMERIC_DSCALE_MAX			NUMERIC_DSCALE_MASK
 
 #define NUMERIC_SIGN(n) \
 	(NUMERIC_IS_SHORT(n) ? \
@@ -2341,7 +2342,11 @@ numeric_mul(PG_FUNCTION_ARGS)
 	 * Unlike add_var() and sub_var(), mul_var() will round its result. In the
 	 * case of numeric_mul(), which is invoked for the * operator on numerics,
 	 * we request exact representation for the product (rscale = sum(dscale of
-	 * arg1, dscale of arg2)).
+	 * arg1, dscale of arg2)).  If the exact result has more digits after the
+	 * decimal point than can be stored in a numeric, we round it.  Rounding
+	 * after computing the exact result ensures that the final result is
+	 * correctly rounded (rounding in mul_var() using a truncated product
+	 * would not guarantee this).
 	 */
 	init_var_from_num(num1, &arg1);
 	init_var_from_num(num2, &arg2);
@@ -2349,6 +2354,9 @@ numeric_mul(PG_FUNCTION_ARGS)
 	init_var(&result);
 	mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
 
+	if (result.dscale > NUMERIC_DSCALE_MAX)
+		round_var(&result, NUMERIC_DSCALE_MAX);
+
 	res = make_result(&result);
 
 	free_var(&result);
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index b4c78a2b722..9291e6dc22d 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -1381,6 +1381,12 @@ select 4769999999999999999999999999999999999999999999999999999999999999999999999
  47699999999999999999999999999999999999999999999999999999999999999999999999999999999999985230000000000000000000000000000000000000000000000000000000000000000000000000000000000001
 (1 row)
 
+select (0.1 - 2e-16383) * (0.1 - 3e-16383) = 0.01 as rounds_to_point_zero_one;
+ rounds_to_point_zero_one 
+--------------------------
+ t
+(1 row)
+
 --
 -- Test some corner cases for division
 --
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 7b3e818e928..da8bef3e764 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -841,6 +841,8 @@ select 4770999999999999999999999999999999999999999999999999999999999999999999999
 
 select 4769999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;
 
+select (0.1 - 2e-16383) * (0.1 - 3e-16383) = 0.01 as rounds_to_point_zero_one;
+
 --
 -- Test some corner cases for division
 --