]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix numeric_mul() overflow due to too many digits after decimal point.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 10 Jul 2021 11:48:59 +0000 (12:48 +0100)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 10 Jul 2021 11:48:59 +0000 (12:48 +0100)
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

src/backend/utils/adt/numeric.c
src/test/regress/expected/numeric.out
src/test/regress/sql/numeric.sql

index fbfae79c355b1243771e7dc674423dd45394c611..38feaeba06fa1b7c6e0844bc3fd1065f6601f31f 100644 (file)
@@ -203,6 +203,7 @@ struct NumericData
  */
 
 #define NUMERIC_DSCALE_MASK                    0x3FFF
+#define NUMERIC_DSCALE_MAX                     NUMERIC_DSCALE_MASK
 
 #define NUMERIC_SIGN(n) \
        (NUMERIC_IS_SHORT(n) ? \
@@ -2488,7 +2489,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);
@@ -2496,6 +2501,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);
index 7ea2c81afaca8b602df72a1bfd03c9bc407cdfdc..bafa02e89ee54fe1dda4471a66b889e26778c75f 100644 (file)
@@ -1498,6 +1498,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
 --
index 4b8a7ca53ebe608e1ad6c9d58534dc56e7f56815..7ab65e971008ec1ff629fe9d10454e043a278f51 100644 (file)
@@ -864,6 +864,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
 --