From b2d47928908d7a99b8e39198d0e8e9e0cb2b024b Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Fri, 3 Feb 2023 11:13:34 +0000 Subject: [PATCH] Make int64_div_fast_to_numeric() more robust. The prior coding of int64_div_fast_to_numeric() had a number of bugs that would cause it to fail under different circumstances, such as with log10val2 <= 0, or log10val2 a multiple of 4, or in the "slow" numeric path with log10val2 >= 10. None of those could be triggered by any of our current code, which only uses log10val2 = 3 or 6. However, they made it a hazard for any future code that might use it. Also, since this is exported by numeric.c, users writing their own C code might choose to use it. Therefore fix, and back-patch to v14, where it was introduced. Dean Rasheed, reviewed by Tom Lane. Discussion: https://postgr.es/m/CAEZATCW8gXgW0tgPxPgHDPhVX71%2BSWFRkhnXy%2BTfGDsKLepu2g%40mail.gmail.com --- src/backend/utils/adt/numeric.c | 73 +++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 08c841675d8..6bf6db6e27b 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4235,7 +4235,7 @@ int64_to_numeric(int64 val) } /* - * Convert val1/(10**val2) to numeric. This is much faster than normal + * Convert val1/(10**log10val2) to numeric. This is much faster than normal * numeric division. */ Numeric @@ -4243,59 +4243,78 @@ int64_div_fast_to_numeric(int64 val1, int log10val2) { Numeric res; NumericVar result; - int64 saved_val1 = val1; + int rscale; int w; int m; + init_var(&result); + + /* result scale */ + rscale = log10val2 < 0 ? 0 : log10val2; + /* how much to decrease the weight by */ w = log10val2 / DEC_DIGITS; - /* how much is left */ + /* how much is left to divide by */ m = log10val2 % DEC_DIGITS; + if (m < 0) + { + m += DEC_DIGITS; + w--; + } /* - * If there is anything left, multiply the dividend by what's left, then - * shift the weight by one more. + * If there is anything left to divide by (10^m with 0 < m < DEC_DIGITS), + * multiply the dividend by 10^(DEC_DIGITS - m), and shift the weight by + * one more. */ if (m > 0) { #if DEC_DIGITS == 4 - static int pow10[] = {1, 10, 100, 1000}; + static const int pow10[] = {1, 10, 100, 1000}; #elif DEC_DIGITS == 2 - static int pow10[] = {1, 10}; + static const int pow10[] = {1, 10}; #elif DEC_DIGITS == 1 - static int pow10[] = {1}; + static const int pow10[] = {1}; #else #error unsupported NBASE #endif + int64 factor = pow10[DEC_DIGITS - m]; + int64 new_val1; StaticAssertDecl(lengthof(pow10) == DEC_DIGITS, "mismatch with DEC_DIGITS"); - if (unlikely(pg_mul_s64_overflow(val1, pow10[DEC_DIGITS - m], &val1))) + if (unlikely(pg_mul_s64_overflow(val1, factor, &new_val1))) { - /* - * If it doesn't fit, do the whole computation in numeric the slow - * way. Note that va1l may have been overwritten, so use - * saved_val1 instead. - */ - int val2 = 1; +#ifdef HAVE_INT128 + /* do the multiplication using 128-bit integers */ + int128 tmp; - for (int i = 0; i < log10val2; i++) - val2 *= 10; - res = numeric_div_opt_error(int64_to_numeric(saved_val1), int64_to_numeric(val2), NULL); - res = DatumGetNumeric(DirectFunctionCall2(numeric_round, - NumericGetDatum(res), - Int32GetDatum(log10val2))); - return res; + tmp = (int128) val1 * (int128) factor; + + int128_to_numericvar(tmp, &result); +#else + /* do the multiplication using numerics */ + NumericVar tmp; + + init_var(&tmp); + + int64_to_numericvar(val1, &result); + int64_to_numericvar(factor, &tmp); + mul_var(&result, &tmp, &result, 0); + + free_var(&tmp); +#endif } + else + int64_to_numericvar(new_val1, &result); + w++; } - - init_var(&result); - - int64_to_numericvar(val1, &result); + else + int64_to_numericvar(val1, &result); result.weight -= w; - result.dscale += w * DEC_DIGITS - (DEC_DIGITS - m); + result.dscale = rscale; res = make_result(&result); -- 2.39.5