Skip to content

Commit

Permalink
Fix numeric_mul() overflow due to too many digits after decimal point.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
deanrasheed committed Jul 10, 2021
1 parent 9ffad7a commit 06883d5
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 1 deletion.
10 changes: 9 additions & 1 deletion src/backend/utils/adt/numeric.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ struct NumericData
*/

#define NUMERIC_DSCALE_MASK 0x3FFF
#define NUMERIC_DSCALE_MAX NUMERIC_DSCALE_MASK

#define NUMERIC_SIGN(n) \
(NUMERIC_IS_SHORT(n) ? \
Expand Down Expand Up @@ -2955,14 +2956,21 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
* 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);

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_opt_error(&result, have_error);

free_var(&result);
Expand Down
6 changes: 6 additions & 0 deletions src/test/regress/expected/numeric.out
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,12 @@ select 4769999999999999999999999999999999999999999999999999999999999999999999999
47699999999999999999999999999999999999999999999999999999999999999999999999999999999999985230000000000000000000000000000000000000000000000000000000000000000000000000000000000001
(1 row)

select trim_scale((0.1 - 2e-16383) * (0.1 - 3e-16383));
trim_scale
------------
0.01
(1 row)

--
-- Test some corner cases for division
--
Expand Down
2 changes: 2 additions & 0 deletions src/test/regress/sql/numeric.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,8 @@ select 4770999999999999999999999999999999999999999999999999999999999999999999999

select 4769999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;

select trim_scale((0.1 - 2e-16383) * (0.1 - 3e-16383));

--
-- Test some corner cases for division
--
Expand Down

0 comments on commit 06883d5

Please sign in to comment.