From 86bfbeab4f439ad527318d9edeb3c71ea46c1ab3 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Fri, 3 Feb 2023 11:09:15 +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 | 80 ++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index e78e0b9247..8cf91776d7 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4089,7 +4089,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 @@ -4097,50 +4097,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) { - static int pow10[] = {1, 10, 100, 1000}; +#if DEC_DIGITS == 4 + static const int pow10[] = {1, 10, 100, 1000}; +#elif DEC_DIGITS == 2 + static const int pow10[] = {1, 10}; +#elif DEC_DIGITS == 1 + static const int pow10[] = {1}; +#else +#error unsupported NBASE +#endif + int64 factor = pow10[DEC_DIGITS - m]; + int64 new_val1; StaticAssertStmt(lengthof(pow10) == DEC_DIGITS, "mismatch with DEC_DIGITS"); - if (unlikely(pg_mul_s64_overflow(val1, pow10[DEC_DIGITS - m], &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; - 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; + if (unlikely(pg_mul_s64_overflow(val1, factor, &new_val1))) + { +#ifdef HAVE_INT128 + /* do the multiplication using 128-bit integers */ + int128 tmp; + + 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);