diff options
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 682200f636b..9525ade1f7c 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -4092,6 +4092,67 @@ int64_to_numeric(int64 val) return res; } +/* + * Convert val1/(10**val2) to numeric. This is much faster than normal + * numeric division. + */ +Numeric +int64_div_fast_to_numeric(int64 val1, int log10val2) +{ + Numeric res; + NumericVar result; + int64 saved_val1 = val1; + int w; + int m; + + /* how much to decrease the weight by */ + w = log10val2 / DEC_DIGITS; + /* how much is left */ + m = log10val2 % DEC_DIGITS; + + /* + * If there is anything left, multiply the dividend by what's left, then + * shift the weight by one more. + */ + if (m > 0) + { + static int pow10[] = {1, 10, 100, 1000}; + + 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; + } + w++; + } + + init_var(&result); + + int64_to_numericvar(val1, &result); + + result.weight -= w; + result.dscale += w * DEC_DIGITS - (DEC_DIGITS - m); + + res = make_result(&result); + + free_var(&result); + + return res; +} + Datum int4_numeric(PG_FUNCTION_ARGS) { |