Skip to content

Commit 4f74741

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 e7c2e02 commit 4f74741

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
@@ -4151,58 +4151,86 @@ int64_to_numeric(int64 val)
41514151
}
41524152

41534153
/*
4154-
* Convert val1/(10**val2) to numeric. This is much faster than normal
4154+
* Convert val1/(10**log10val2) to numeric. This is much faster than normal
41554155
* numeric division.
41564156
*/
41574157
Numeric
41584158
int64_div_fast_to_numeric(int64 val1, int log10val2)
41594159
{
41604160
Numeric res;
41614161
NumericVar result;
4162-
int64 saved_val1 = val1;
4162+
int rscale;
41634163
int w;
41644164
int m;
41654165

4166+
init_var(&result);
4167+
4168+
/* result scale */
4169+
rscale = log10val2 < 0 ? 0 : log10val2;
4170+
41664171
/* how much to decrease the weight by */
41674172
w = log10val2 / DEC_DIGITS;
4168-
/* how much is left */
4173+
/* how much is left to divide by */
41694174
m = log10val2 % DEC_DIGITS;
4175+
if (m < 0)
4176+
{
4177+
m += DEC_DIGITS;
4178+
w--;
4179+
}
41704180

41714181
/*
4172-
* If there is anything left, multiply the dividend by what's left, then
4173-
* shift the weight by one more.
4182+
* If there is anything left to divide by (10^m with 0 < m < DEC_DIGITS),
4183+
* multiply the dividend by 10^(DEC_DIGITS - m), and shift the weight by
4184+
* one more.
41744185
*/
41754186
if (m > 0)
41764187
{
4177-
static int pow10[] = {1, 10, 100, 1000};
4188+
#if DEC_DIGITS == 4
4189+
static const int pow10[] = {1, 10, 100, 1000};
4190+
#elif DEC_DIGITS == 2
4191+
static const int pow10[] = {1, 10};
4192+
#elif DEC_DIGITS == 1
4193+
static const int pow10[] = {1};
4194+
#else
4195+
#error unsupported NBASE
4196+
#endif
4197+
int64 factor = pow10[DEC_DIGITS - m];
4198+
int64 new_val1;
41784199

41794200
StaticAssertStmt(lengthof(pow10) == DEC_DIGITS, "mismatch with DEC_DIGITS");
4180-
if (unlikely(pg_mul_s64_overflow(val1, pow10[DEC_DIGITS - m], &val1)))
4201+
4202+
if (unlikely(pg_mul_s64_overflow(val1, factor, &new_val1)))
41814203
{
4182-
/*
4183-
* If it doesn't fit, do the whole computation in numeric the slow
4184-
* way. Note that va1l may have been overwritten, so use
4185-
* saved_val1 instead.
4186-
*/
4187-
int val2 = 1;
4188-
4189-
for (int i = 0; i < log10val2; i++)
4190-
val2 *= 10;
4191-
res = numeric_div_opt_error(int64_to_numeric(saved_val1), int64_to_numeric(val2), NULL);
4192-
res = DatumGetNumeric(DirectFunctionCall2(numeric_round,
4193-
NumericGetDatum(res),
4194-
Int32GetDatum(log10val2)));
4195-
return res;
4204+
#ifdef HAVE_INT128
4205+
/* do the multiplication using 128-bit integers */
4206+
int128 tmp;
4207+
4208+
tmp = (int128) val1 * (int128) factor;
4209+
4210+
int128_to_numericvar(tmp, &result);
4211+
#else
4212+
/* do the multiplication using numerics */
4213+
NumericVar tmp;
4214+
4215+
init_var(&tmp);
4216+
4217+
int64_to_numericvar(val1, &result);
4218+
int64_to_numericvar(factor, &tmp);
4219+
mul_var(&result, &tmp, &result, 0);
4220+
4221+
free_var(&tmp);
4222+
#endif
41964223
}
4224+
else
4225+
int64_to_numericvar(new_val1, &result);
4226+
41974227
w++;
41984228
}
4199-
4200-
init_var(&result);
4201-
4202-
int64_to_numericvar(val1, &result);
4229+
else
4230+
int64_to_numericvar(val1, &result);
42034231

42044232
result.weight -= w;
4205-
result.dscale += w * DEC_DIGITS - (DEC_DIGITS - m);
4233+
result.dscale = rscale;
42064234

42074235
res = make_result(&result);
42084236

0 commit comments

Comments
 (0)