Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit b2d4792

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 2010d8b commit b2d4792

File tree

1 file changed

+46
-27
lines changed

1 file changed

+46
-27
lines changed

src/backend/utils/adt/numeric.c

+46-27
Original file line numberDiff line numberDiff line change
@@ -4235,67 +4235,86 @@ int64_to_numeric(int64 val)
42354235
}
42364236

42374237
/*
4238-
* Convert val1/(10**val2) to numeric. This is much faster than normal
4238+
* Convert val1/(10**log10val2) to numeric. This is much faster than normal
42394239
* numeric division.
42404240
*/
42414241
Numeric
42424242
int64_div_fast_to_numeric(int64 val1, int log10val2)
42434243
{
42444244
Numeric res;
42454245
NumericVar result;
4246-
int64 saved_val1 = val1;
4246+
int rscale;
42474247
int w;
42484248
int m;
42494249

4250+
init_var(&result);
4251+
4252+
/* result scale */
4253+
rscale = log10val2 < 0 ? 0 : log10val2;
4254+
42504255
/* how much to decrease the weight by */
42514256
w = log10val2 / DEC_DIGITS;
4252-
/* how much is left */
4257+
/* how much is left to divide by */
42534258
m = log10val2 % DEC_DIGITS;
4259+
if (m < 0)
4260+
{
4261+
m += DEC_DIGITS;
4262+
w--;
4263+
}
42544264

42554265
/*
4256-
* If there is anything left, multiply the dividend by what's left, then
4257-
* shift the weight by one more.
4266+
* If there is anything left to divide by (10^m with 0 < m < DEC_DIGITS),
4267+
* multiply the dividend by 10^(DEC_DIGITS - m), and shift the weight by
4268+
* one more.
42584269
*/
42594270
if (m > 0)
42604271
{
42614272
#if DEC_DIGITS == 4
4262-
static int pow10[] = {1, 10, 100, 1000};
4273+
static const int pow10[] = {1, 10, 100, 1000};
42634274
#elif DEC_DIGITS == 2
4264-
static int pow10[] = {1, 10};
4275+
static const int pow10[] = {1, 10};
42654276
#elif DEC_DIGITS == 1
4266-
static int pow10[] = {1};
4277+
static const int pow10[] = {1};
42674278
#else
42684279
#error unsupported NBASE
42694280
#endif
4281+
int64 factor = pow10[DEC_DIGITS - m];
4282+
int64 new_val1;
42704283

42714284
StaticAssertDecl(lengthof(pow10) == DEC_DIGITS, "mismatch with DEC_DIGITS");
42724285

4273-
if (unlikely(pg_mul_s64_overflow(val1, pow10[DEC_DIGITS - m], &val1)))
4286+
if (unlikely(pg_mul_s64_overflow(val1, factor, &new_val1)))
42744287
{
4275-
/*
4276-
* If it doesn't fit, do the whole computation in numeric the slow
4277-
* way. Note that va1l may have been overwritten, so use
4278-
* saved_val1 instead.
4279-
*/
4280-
int val2 = 1;
4288+
#ifdef HAVE_INT128
4289+
/* do the multiplication using 128-bit integers */
4290+
int128 tmp;
42814291

4282-
for (int i = 0; i < log10val2; i++)
4283-
val2 *= 10;
4284-
res = numeric_div_opt_error(int64_to_numeric(saved_val1), int64_to_numeric(val2), NULL);
4285-
res = DatumGetNumeric(DirectFunctionCall2(numeric_round,
4286-
NumericGetDatum(res),
4287-
Int32GetDatum(log10val2)));
4288-
return res;
4292+
tmp = (int128) val1 * (int128) factor;
4293+
4294+
int128_to_numericvar(tmp, &result);
4295+
#else
4296+
/* do the multiplication using numerics */
4297+
NumericVar tmp;
4298+
4299+
init_var(&tmp);
4300+
4301+
int64_to_numericvar(val1, &result);
4302+
int64_to_numericvar(factor, &tmp);
4303+
mul_var(&result, &tmp, &result, 0);
4304+
4305+
free_var(&tmp);
4306+
#endif
42894307
}
4308+
else
4309+
int64_to_numericvar(new_val1, &result);
4310+
42904311
w++;
42914312
}
4292-
4293-
init_var(&result);
4294-
4295-
int64_to_numericvar(val1, &result);
4313+
else
4314+
int64_to_numericvar(val1, &result);
42964315

42974316
result.weight -= w;
4298-
result.dscale += w * DEC_DIGITS - (DEC_DIGITS - m);
4317+
result.dscale = rscale;
42994318

43004319
res = make_result(&result);
43014320

0 commit comments

Comments
 (0)