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
This commit is contained in:
Dean Rasheed 2023-02-03 11:09:15 +00:00
parent 89d28f928c
commit 86bfbeab4f
1 changed files with 54 additions and 26 deletions

View File

@ -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);