diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index d0f0923710..94752fc343 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -3016,6 +3016,25 @@ numeric_mul(PG_FUNCTION_ARGS) } +/* + * numeric_mul_rscale() - + * + * Calculate the product of two numerics with reduced rscale. + */ +Datum +numeric_mul_rscale(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + int rscale_adjustment = PG_GETARG_INT32(2); + Numeric res; + + res = numeric_mul_rscale_opt_error(num1, num2, NULL, rscale_adjustment); + + PG_RETURN_NUMERIC(res); +} + + /* * numeric_mul_opt_error() - * @@ -3119,6 +3138,108 @@ numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error) } +/* + * numeric_mul_rscale_opt_error() - + * + * Internal version of numeric_mul_rscale(). If "*have_error" flag is provided, + * on error it's set to true, NULL returned. This is helpful when caller + * need to handle errors by itself. + */ +Numeric +numeric_mul_rscale_opt_error(Numeric num1, Numeric num2, bool *have_error, int rscale_adjustment) +{ + NumericVar arg1; + NumericVar arg2; + NumericVar result; + Numeric res; + + /* + * Handle NaN and infinities + */ + if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2)) + { + if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) + return make_result(&const_nan); + if (NUMERIC_IS_PINF(num1)) + { + switch (numeric_sign_internal(num2)) + { + case 0: + return make_result(&const_nan); /* Inf * 0 */ + case 1: + return make_result(&const_pinf); + case -1: + return make_result(&const_ninf); + } + Assert(false); + } + if (NUMERIC_IS_NINF(num1)) + { + switch (numeric_sign_internal(num2)) + { + case 0: + return make_result(&const_nan); /* -Inf * 0 */ + case 1: + return make_result(&const_ninf); + case -1: + return make_result(&const_pinf); + } + Assert(false); + } + /* by here, num1 must be finite, so num2 is not */ + if (NUMERIC_IS_PINF(num2)) + { + switch (numeric_sign_internal(num1)) + { + case 0: + return make_result(&const_nan); /* 0 * Inf */ + case 1: + return make_result(&const_pinf); + case -1: + return make_result(&const_ninf); + } + Assert(false); + } + Assert(NUMERIC_IS_NINF(num2)); + switch (numeric_sign_internal(num1)) + { + case 0: + return make_result(&const_nan); /* 0 * -Inf */ + case 1: + return make_result(&const_ninf); + case -1: + return make_result(&const_pinf); + } + Assert(false); + } + + /* + * Unpack the values, let mul_var() compute the result and return it. + * 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)). 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 + rscale_adjustment); + + if (result.dscale > NUMERIC_DSCALE_MAX) + round_var(&result, NUMERIC_DSCALE_MAX); + + res = make_result_opt_error(&result, have_error); + + free_var(&result); + + return res; +} + /* * numeric_div() - * @@ -8719,6 +8840,11 @@ mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, return; } + if (rscale != var1->dscale + var2->dscale) + { + printf("NUMERIC_REDUCED_RSCALE %d,%d,%d,%d,%d\n", var1ndigits, var2ndigits, var1->dscale, var2->dscale, rscale - (var1->dscale + var2->dscale)); + } + /* * If var1 has 1-4 digits and the exact result was requested, delegate to * mul_var_short() which uses a faster direct multiplication algorithm. diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index d36f6001bb..a400ff2875 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -4498,6 +4498,9 @@ { oid => '1726', proname => 'numeric_mul', prorettype => 'numeric', proargtypes => 'numeric numeric', prosrc => 'numeric_mul' }, +{ oid => '8000', + proname => 'numeric_mul_rscale', prorettype => 'numeric', + proargtypes => 'numeric numeric int4', prosrc => 'numeric_mul_rscale' }, { oid => '1727', proname => 'numeric_div', prorettype => 'numeric', proargtypes => 'numeric numeric', prosrc => 'numeric_div' }, diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h index 43c75c436f..76794970d1 100644 --- a/src/include/utils/numeric.h +++ b/src/include/utils/numeric.h @@ -97,6 +97,8 @@ extern Numeric numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error); extern Numeric numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error); +extern Numeric numeric_mul_rscale_opt_error(Numeric num1, Numeric num2, + bool *have_error, int rscale_adjustment); extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error); extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,