diff options
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index b818189d869..5510a203b03 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -583,6 +583,8 @@ static void power_var(const NumericVar *base, const NumericVar *exp, static void power_var_int(const NumericVar *base, int exp, int exp_dscale, NumericVar *result); static void power_ten_int(int exp, NumericVar *result); +static void random_var(pg_prng_state *state, const NumericVar *rmin, + const NumericVar *rmax, NumericVar *result); static int cmp_abs(const NumericVar *var1, const NumericVar *var2); static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits, @@ -4219,6 +4221,56 @@ numeric_trim_scale(PG_FUNCTION_ARGS) PG_RETURN_NUMERIC(res); } +/* + * Return a random numeric value in the range [rmin, rmax]. + */ +Numeric +random_numeric(pg_prng_state *state, Numeric rmin, Numeric rmax) +{ + NumericVar rmin_var; + NumericVar rmax_var; + NumericVar result; + Numeric res; + + /* Range bounds must not be NaN/infinity */ + if (NUMERIC_IS_SPECIAL(rmin)) + { + if (NUMERIC_IS_NAN(rmin)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lower bound cannot be NaN")); + else + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lower bound cannot be infinity")); + } + if (NUMERIC_IS_SPECIAL(rmax)) + { + if (NUMERIC_IS_NAN(rmax)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("upper bound cannot be NaN")); + else + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("upper bound cannot be infinity")); + } + + /* Return a random value in the range [rmin, rmax] */ + init_var_from_num(rmin, &rmin_var); + init_var_from_num(rmax, &rmax_var); + + init_var(&result); + + random_var(state, &rmin_var, &rmax_var, &result); + + res = make_result(&result); + + free_var(&result); + + return res; +} + /* ---------------------------------------------------------------------- * @@ -11262,6 +11314,173 @@ power_ten_int(int exp, NumericVar *result) result->digits[0] *= 10; } +/* + * random_var() - return a random value in the range [rmin, rmax]. + */ +static void +random_var(pg_prng_state *state, const NumericVar *rmin, + const NumericVar *rmax, NumericVar *result) +{ + int rscale; + NumericVar rlen; + int res_ndigits; + int n; + int pow10; + int i; + uint64 rlen64; + int rlen64_ndigits; + + rscale = Max(rmin->dscale, rmax->dscale); + + /* Compute rlen = rmax - rmin and check the range bounds */ + init_var(&rlen); + sub_var(rmax, rmin, &rlen); + + if (rlen.sign == NUMERIC_NEG) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("lower bound must be less than or equal to upper bound")); + + /* Special case for an empty range */ + if (rlen.ndigits == 0) + { + set_var_from_var(rmin, result); + result->dscale = rscale; + free_var(&rlen); + return; + } + + /* + * Otherwise, select a random value in the range [0, rlen = rmax - rmin], + * and shift it to the required range by adding rmin. + */ + + /* Required result digits */ + res_ndigits = rlen.weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS; + + /* + * To get the required rscale, the final result digit must be a multiple + * of pow10 = 10^n, where n = (-rscale) mod DEC_DIGITS. + */ + n = ((rscale + DEC_DIGITS - 1) / DEC_DIGITS) * DEC_DIGITS - rscale; + pow10 = 1; + for (i = 0; i < n; i++) + pow10 *= 10; + + /* + * To choose a random value uniformly from the range [0, rlen], we choose + * from the slightly larger range [0, rlen2], where rlen2 is formed from + * rlen by copying the first 4 NBASE digits, and setting all remaining + * decimal digits to "9". + * + * Without loss of generality, we can ignore the weight of rlen2 and treat + * it as a pure integer for the purposes of this discussion. The process + * above gives rlen2 + 1 = rlen64 * 10^N, for some integer N, where rlen64 + * is a 64-bit integer formed from the first 4 NBASE digits copied from + * rlen. Since this trivially factors into smaller pieces that fit in + * 64-bit integers, the task of choosing a random value uniformly from the + * rlen2 + 1 possible values in [0, rlen2] is much simpler. + * + * If the random value selected is too large, it is rejected, and we try + * again until we get a result <= rlen, ensuring that the overall result + * is uniform (no particular value is any more likely than any other). + * + * Since rlen64 holds 4 NBASE digits from rlen, it contains at least + * DEC_DIGITS * 3 + 1 decimal digits (i.e., at least 13 decimal digits, + * when DEC_DIGITS is 4). Therefore the probability of needing to reject + * the value chosen and retry is less than 1e-13. + */ + rlen64 = (uint64) rlen.digits[0]; + rlen64_ndigits = 1; + while (rlen64_ndigits < res_ndigits && rlen64_ndigits < 4) + { + rlen64 *= NBASE; + if (rlen64_ndigits < rlen.ndigits) + rlen64 += rlen.digits[rlen64_ndigits]; + rlen64_ndigits++; + } + + /* Loop until we get a result <= rlen */ + do + { + NumericDigit *res_digits; + uint64 rand; + int whole_ndigits; + + alloc_var(result, res_ndigits); + result->sign = NUMERIC_POS; + result->weight = rlen.weight; + result->dscale = rscale; + res_digits = result->digits; + + /* + * Set the first rlen64_ndigits using a random value in [0, rlen64]. + * + * If this is the whole result, and rscale is not a multiple of + * DEC_DIGITS (pow10 from above is not 1), then we need this to be a + * multiple of pow10. + */ + if (rlen64_ndigits == res_ndigits && pow10 != 1) + rand = pg_prng_uint64_range(state, 0, rlen64 / pow10) * pow10; + else + rand = pg_prng_uint64_range(state, 0, rlen64); + + for (i = rlen64_ndigits - 1; i >= 0; i--) + { + res_digits[i] = (NumericDigit) (rand % NBASE); + rand = rand / NBASE; + } + + /* + * Set the remaining digits to random values in range [0, NBASE), + * noting that the last digit needs to be a multiple of pow10. + */ + whole_ndigits = res_ndigits; + if (pow10 != 1) + whole_ndigits--; + + /* Set whole digits in groups of 4 for best performance */ + i = rlen64_ndigits; + while (i < whole_ndigits - 3) + { + rand = pg_prng_uint64_range(state, 0, + (uint64) NBASE * NBASE * NBASE * NBASE - 1); + res_digits[i++] = (NumericDigit) (rand % NBASE); + rand = rand / NBASE; + res_digits[i++] = (NumericDigit) (rand % NBASE); + rand = rand / NBASE; + res_digits[i++] = (NumericDigit) (rand % NBASE); + rand = rand / NBASE; + res_digits[i++] = (NumericDigit) rand; + } + + /* Remaining whole digits */ + while (i < whole_ndigits) + { + rand = pg_prng_uint64_range(state, 0, NBASE - 1); + res_digits[i++] = (NumericDigit) rand; + } + + /* Final partial digit (multiple of pow10) */ + if (i < res_ndigits) + { + rand = pg_prng_uint64_range(state, 0, NBASE / pow10 - 1) * pow10; + res_digits[i] = (NumericDigit) rand; + } + + /* Remove leading/trailing zeroes */ + strip_var(result); + + /* If result > rlen, try again */ + + } while (cmp_var(result, &rlen) > 0); + + /* Offset the result to the required range */ + add_var(result, rmin, result); + + free_var(&rlen); +} + /* ---------------------------------------------------------------------- * |