Skip to content

Commit 357b66e

Browse files
committed
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
1 parent e82cde7 commit 357b66e

File tree

3 files changed

+17
-1
lines changed

3 files changed

+17
-1
lines changed

src/backend/utils/adt/numeric.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ struct NumericData
205205
*/
206206

207207
#define NUMERIC_DSCALE_MASK 0x3FFF
208+
#define NUMERIC_DSCALE_MAX NUMERIC_DSCALE_MASK
208209

209210
#define NUMERIC_SIGN(n) \
210211
(NUMERIC_IS_SHORT(n) ? \
@@ -2555,14 +2556,21 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
25552556
* Unlike add_var() and sub_var(), mul_var() will round its result. In the
25562557
* case of numeric_mul(), which is invoked for the * operator on numerics,
25572558
* we request exact representation for the product (rscale = sum(dscale of
2558-
* arg1, dscale of arg2)).
2559+
* arg1, dscale of arg2)). If the exact result has more digits after the
2560+
* decimal point than can be stored in a numeric, we round it. Rounding
2561+
* after computing the exact result ensures that the final result is
2562+
* correctly rounded (rounding in mul_var() using a truncated product
2563+
* would not guarantee this).
25592564
*/
25602565
init_var_from_num(num1, &arg1);
25612566
init_var_from_num(num2, &arg2);
25622567

25632568
init_var(&result);
25642569
mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
25652570

2571+
if (result.dscale > NUMERIC_DSCALE_MAX)
2572+
round_var(&result, NUMERIC_DSCALE_MAX);
2573+
25662574
res = make_result_opt_error(&result, have_error);
25672575

25682576
free_var(&result);

src/test/regress/expected/numeric.out

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,12 @@ select 4769999999999999999999999999999999999999999999999999999999999999999999999
14981498
47699999999999999999999999999999999999999999999999999999999999999999999999999999999999985230000000000000000000000000000000000000000000000000000000000000000000000000000000000001
14991499
(1 row)
15001500

1501+
select (0.1 - 2e-16383) * (0.1 - 3e-16383) = 0.01 as rounds_to_point_zero_one;
1502+
rounds_to_point_zero_one
1503+
--------------------------
1504+
t
1505+
(1 row)
1506+
15011507
--
15021508
-- Test some corner cases for division
15031509
--

src/test/regress/sql/numeric.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,8 @@ select 4770999999999999999999999999999999999999999999999999999999999999999999999
864864

865865
select 4769999999999999999999999999999999999999999999999999999999999999999999999999999999999999 * 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999;
866866

867+
select (0.1 - 2e-16383) * (0.1 - 3e-16383) = 0.01 as rounds_to_point_zero_one;
868+
867869
--
868870
-- Test some corner cases for division
869871
--

0 commit comments

Comments
 (0)