postgres.git / summary / log / commit / refs

commit    54a0ec1bd6d5f46938b8da7bbcf7c3cb26b8638d
Author:   Dean Rasheed <dean.a.rasheed@gmail.com>
Date:     Sat Jul 10 11:50:05 2021 +0000
First released in: 15.0, 9.6.23, 10.18, 11.13, 12.8, 13.4, 14.0

    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


src/backend/utils/adt/numeric.c | 10 +++++++++- src/test/regress/expected/numeric.out | 6 ++++++ src/test/regress/sql/numeric.sql | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) 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 -- [parent: 56533fce7345]