Skip to content

Commit 86bfbea

Browse files
committed
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
1 parent 89d28f9 commit 86bfbea

File tree

1 file changed

+54
-26
lines changed

1 file changed

+54
-26
lines changed

src/backend/utils/adt/numeric.c

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4089,58 +4089,86 @@ int64_to_numeric(int64 val)
40894089
}
40904090

40914091
/*
4092-
* Convert val1/(10**val2) to numeric. This is much faster than normal
4092+
* Convert val1/(10**log10val2) to numeric. This is much faster than normal
40934093
* numeric division.
40944094
*/
40954095
Numeric
40964096
int64_div_fast_to_numeric(int64 val1, int log10val2)
40974097
{
40984098
Numeric res;
40994099
NumericVar result;
4100-
int64 saved_val1 = val1;
4100+
int rscale;
41014101
int w;
41024102
int m;
41034103

4104+
init_var(&result);
4105+
4106+
/* result scale */
4107+
rscale = log10val2 < 0 ? 0 : log10val2;
4108+
41044109
/* how much to decrease the weight by */
41054110
w = log10val2 / DEC_DIGITS;
4106-
/* how much is left */
4111+
/* how much is left to divide by */
41074112
m = log10val2 % DEC_DIGITS;
4113+
if (m < 0)
4114+
{
4115+
m += DEC_DIGITS;
4116+
w--;
4117+
}
41084118

41094119
/*
4110-
* If there is anything left, multiply the dividend by what's left, then
4111-
* shift the weight by one more.
4120+
* If there is anything left to divide by (10^m with 0 < m < DEC_DIGITS),
4121+
* multiply the dividend by 10^(DEC_DIGITS - m), and shift the weight by
4122+
* one more.
41124123
*/
41134124
if (m > 0)
41144125
{
4115-
static int pow10[] = {1, 10, 100, 1000};
4126+
#if DEC_DIGITS == 4
4127+
static const int pow10[] = {1, 10, 100, 1000};
4128+
#elif DEC_DIGITS == 2
4129+
static const int pow10[] = {1, 10};
4130+
#elif DEC_DIGITS == 1
4131+
static const int pow10[] = {1};
4132+
#else
4133+
#error unsupported NBASE
4134+
#endif
4135+
int64 factor = pow10[DEC_DIGITS - m];
4136+
int64 new_val1;
41164137

41174138
StaticAssertStmt(lengthof(pow10) == DEC_DIGITS, "mismatch with DEC_DIGITS");
4118-
if (unlikely(pg_mul_s64_overflow(val1, pow10[DEC_DIGITS - m], &val1)))
4139+
4140+
if (unlikely(pg_mul_s64_overflow(val1, factor, &new_val1)))
41194141
{
4120-
/*
4121-
* If it doesn't fit, do the whole computation in numeric the slow
4122-
* way. Note that va1l may have been overwritten, so use
4123-
* saved_val1 instead.
4124-
*/
4125-
int val2 = 1;
4126-
4127-
for (int i = 0; i < log10val2; i++)
4128-
val2 *= 10;
4129-
res = numeric_div_opt_error(int64_to_numeric(saved_val1), int64_to_numeric(val2), NULL);
4130-
res = DatumGetNumeric(DirectFunctionCall2(numeric_round,
4131-
NumericGetDatum(res),
4132-
Int32GetDatum(log10val2)));
4133-
return res;
4142+
#ifdef HAVE_INT128
4143+
/* do the multiplication using 128-bit integers */
4144+
int128 tmp;
4145+
4146+
tmp = (int128) val1 * (int128) factor;
4147+
4148+
int128_to_numericvar(tmp, &result);
4149+
#else
4150+
/* do the multiplication using numerics */
4151+
NumericVar tmp;
4152+
4153+
init_var(&tmp);
4154+
4155+
int64_to_numericvar(val1, &result);
4156+
int64_to_numericvar(factor, &tmp);
4157+
mul_var(&result, &tmp, &result, 0);
4158+
4159+
free_var(&tmp);
4160+
#endif
41344161
}
4162+
else
4163+
int64_to_numericvar(new_val1, &result);
4164+
41354165
w++;
41364166
}
4137-
4138-
init_var(&result);
4139-
4140-
int64_to_numericvar(val1, &result);
4167+
else
4168+
int64_to_numericvar(val1, &result);
41414169

41424170
result.weight -= w;
4143-
result.dscale += w * DEC_DIGITS - (DEC_DIGITS - m);
4171+
result.dscale = rscale;
41444172

41454173
res = make_result(&result);
41464174

0 commit comments

Comments
 (0)