diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c new file mode 100644 index 5510a20..81600b3 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -551,6 +551,8 @@ static void sub_var(const NumericVar *va static void mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, int rscale); +static void mul_var_small(const NumericVar *var1, const NumericVar *var2, + NumericVar *result, int rscale); static void div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, int rscale, bool round); @@ -8707,7 +8709,7 @@ mul_var(const NumericVar *var1, const Nu var1digits = var1->digits; var2digits = var2->digits; - if (var1ndigits == 0 || var2ndigits == 0) + if (var1ndigits == 0) { /* one or both inputs is zero; so is result */ zero_var(result); @@ -8715,6 +8717,16 @@ mul_var(const NumericVar *var1, const Nu return; } + /* + * If var1 has 3 digits or fewer, delegate to mul_var_small() which uses a + * faster short multiplication algorithm. + */ + if (var1ndigits <= 3) + { + mul_var_small(var1, var2, result, rscale); + return; + } + /* Determine result sign and (maximum possible) weight */ if (var1->sign == var2->sign) res_sign = NUMERIC_POS; @@ -8858,6 +8870,188 @@ mul_var(const NumericVar *var1, const Nu result->sign = res_sign; /* Round to target rscale (and set result->dscale) */ + round_var(result, rscale); + + /* Strip leading and trailing zeroes */ + strip_var(result); +} + + +/* + * mul_var_small() - + * + * This has the same API as mul_var, but it assumes that var1 has no more + * than 3 digits and var2 has at least as many digits as var1. For variables + * satisfying these conditions, the product can be computed more quickly than + * the general algorithm used in mul_var. + */ +static void +mul_var_small(const NumericVar *var1, const NumericVar *var2, + NumericVar *result, int rscale) +{ + int var1ndigits = var1->ndigits; + int var2ndigits = var2->ndigits; + NumericDigit *var1digits = var1->digits; + NumericDigit *var2digits = var2->digits; + int res_sign; + int res_weight; + int res_ndigits; + int maxdigits; + NumericDigit *res_buf; + NumericDigit *res_digits; + int carry; + int term; + + /* Check preconditions */ + Assert(var1ndigits <= 3); + Assert(var2ndigits >= var1ndigits); + + /* Determine result sign and (maximum possible) weight */ + if (var1->sign == var2->sign) + res_sign = NUMERIC_POS; + else + res_sign = NUMERIC_NEG; + res_weight = var1->weight + var2->weight + 2; + + /* Determine the number of result digits to compute - see mul_var() */ + res_ndigits = var1ndigits + var2ndigits + 1; + maxdigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS + + MUL_GUARD_DIGITS; + res_ndigits = Min(res_ndigits, maxdigits); + + if (res_ndigits < 3) + { + /* All input digits will be ignored; so result is zero */ + zero_var(result); + result->dscale = rscale; + return; + } + + /* Allocate result digit array */ + res_buf = digitbuf_alloc(res_ndigits); + res_buf[0] = 0; /* spare digit for later rounding */ + res_digits = res_buf + 1; + + /* + * Compute the result digits in reverse, in one pass, propagating the + * carry up as we go. + * + * This computes res_digits[res_ndigits - 2], ... res_digits[0] by summing + * the products var1digits[i1] * var2digits[i2] for which i1 + i2 + 1 is + * the result index. + */ + switch (var1ndigits) + { + case 1: + /* --------- + * 1-digit case: + * var1ndigits = 1 + * var2ndigits >= 1 + * 3 <= res_ndigits <= var2ndigits + 2 + * ---------- + */ + carry = 0; + for (int i = res_ndigits - 3; i >= 0; i--) + { + term = (int) var1digits[0] * var2digits[i] + carry; + res_digits[i + 1] = (NumericDigit) (term % NBASE); + carry = term / NBASE; + } + res_digits[0] = (NumericDigit) carry; + break; + + case 2: + /* --------- + * 2-digit case: + * var1ndigits = 2 + * var2ndigits >= 2 + * 3 <= res_ndigits <= var2ndigits + 3 + * ---------- + */ + /* last result digit and carry */ + term = 0; + if (res_ndigits - 3 < var2ndigits) + term += (int) var1digits[0] * var2digits[res_ndigits - 3]; + if (res_ndigits > 3) + term += (int) var1digits[1] * var2digits[res_ndigits - 4]; + res_digits[res_ndigits - 2] = (NumericDigit) (term % NBASE); + carry = term / NBASE; + + /* remaining digits, except for the first two */ + for (int i = res_ndigits - 4; i >= 1; i--) + { + term = (int) var1digits[0] * var2digits[i] + + (int) var1digits[1] * var2digits[i - 1] + carry; + res_digits[i + 1] = (NumericDigit) (term % NBASE); + carry = term / NBASE; + } + + /* first two digits */ + term = (int) var1digits[0] * var2digits[0] + carry; + res_digits[1] = (NumericDigit) (term % NBASE); + res_digits[0] = (NumericDigit) (term / NBASE); + break; + + case 3: + /* --------- + * 3-digit case: + * var1ndigits = 3 + * var2ndigits >= 3 + * 3 <= res_ndigits <= var2ndigits + 4 + * ---------- + */ + /* last result digit and carry */ + term = 0; + if (res_ndigits - 3 < var2ndigits) + term += (int) var1digits[0] * var2digits[res_ndigits - 3]; + if (res_ndigits > 3 && res_ndigits - 4 < var2ndigits) + term += (int) var1digits[1] * var2digits[res_ndigits - 4]; + if (res_ndigits > 4) + term += (int) var1digits[2] * var2digits[res_ndigits - 5]; + res_digits[res_ndigits - 2] = (NumericDigit) (term % NBASE); + carry = term / NBASE; + + /* penultimate result digit */ + term = carry; + if (res_ndigits > 3 && res_ndigits - 4 < var2ndigits) + term += (int) var1digits[0] * var2digits[res_ndigits - 4]; + if (res_ndigits > 4) + term += (int) var1digits[1] * var2digits[res_ndigits - 5]; + if (res_ndigits > 5) + term += (int) var1digits[2] * var2digits[res_ndigits - 6]; + res_digits[res_ndigits - 3] = (NumericDigit) (term % NBASE); + carry = term / NBASE; + + /* remaining digits, except for the first three */ + for (int i = res_ndigits - 5; i >= 2; i--) + { + term = (int) var1digits[0] * var2digits[i] + + (int) var1digits[1] * var2digits[i - 1] + + (int) var1digits[2] * var2digits[i - 2] + carry; + res_digits[i + 1] = (NumericDigit) (term % NBASE); + carry = term / NBASE; + } + + /* first three digits */ + term = (int) var1digits[0] * var2digits[1] + + (int) var1digits[1] * var2digits[0] + carry; + res_digits[2] = (NumericDigit) (term % NBASE); + carry = term / NBASE; + term = (int) var1digits[0] * var2digits[0] + carry; + res_digits[1] = (NumericDigit) (term % NBASE); + res_digits[0] = (NumericDigit) (term / NBASE); + break; + } + + /* Store the product in result (minus extra rounding digit) */ + digitbuf_free(result->buf); + result->ndigits = res_ndigits - 1; + result->buf = res_buf; + result->digits = res_digits; + result->weight = res_weight - 1; + result->sign = res_sign; + + /* Round to target rscale (and set result->dscale) */ round_var(result, rscale); /* Strip leading and trailing zeroes */