mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-02-23 19:39:53 +08:00
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:
parent
e7c2e02a66
commit
4f74741a5c
@ -4151,7 +4151,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
|
||||
@ -4159,50 +4159,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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user