1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-27 23:21:58 +03:00

Add functions gcd() and lcm() for integer and numeric types.

These compute the greatest common divisor and least common multiple of
a pair of numbers using the Euclidean algorithm.

Vik Fearing, reviewed by Fabien Coelho.

Discussion: https://postgr.es/m/adbd3e0b-e3f1-5bbc-21db-03caf1cef0f7@2ndquadrant.com
This commit is contained in:
Dean Rasheed
2020-01-25 14:00:59 +00:00
parent 530609aa42
commit 13661ddd7e
12 changed files with 689 additions and 1 deletions

View File

@ -521,6 +521,8 @@ static void mod_var(const NumericVar *var1, const NumericVar *var2,
static void ceil_var(const NumericVar *var, NumericVar *result);
static void floor_var(const NumericVar *var, NumericVar *result);
static void gcd_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *result);
static void sqrt_var(const NumericVar *arg, NumericVar *result, int rscale);
static void exp_var(const NumericVar *arg, NumericVar *result, int rscale);
static int estimate_ln_dweight(const NumericVar *var);
@ -2838,6 +2840,107 @@ numeric_larger(PG_FUNCTION_ARGS)
* ----------------------------------------------------------------------
*/
/*
* numeric_gcd() -
*
* Calculate the greatest common divisor of two numerics
*/
Datum
numeric_gcd(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Unpack the arguments
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
* Find the GCD and return the result
*/
gcd_var(&arg1, &arg2, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_lcm() -
*
* Calculate the least common multiple of two numerics
*/
Datum
numeric_lcm(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Unpack the arguments
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
* Compute the result using lcm(x, y) = abs(x / gcd(x, y) * y), returning
* zero if either input is zero.
*
* Note that the division is guaranteed to be exact, returning an integer
* result, so the LCM is an integral multiple of both x and y. A display
* scale of Min(x.dscale, y.dscale) would be sufficient to represent it,
* but as with other numeric functions, we choose to return a result whose
* display scale is no smaller than either input.
*/
if (arg1.ndigits == 0 || arg2.ndigits == 0)
set_var_from_var(&const_zero, &result);
else
{
gcd_var(&arg1, &arg2, &result);
div_var(&arg1, &result, &result, 0, false);
mul_var(&arg2, &result, &result, arg2.dscale);
result.sign = NUMERIC_POS;
}
result.dscale = Max(arg1.dscale, arg2.dscale);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_fac()
*
@ -8039,6 +8142,74 @@ floor_var(const NumericVar *var, NumericVar *result)
}
/*
* gcd_var() -
*
* Calculate the greatest common divisor of two numerics at variable level
*/
static void
gcd_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
{
int res_dscale;
int cmp;
NumericVar tmp_arg;
NumericVar mod;
res_dscale = Max(var1->dscale, var2->dscale);
/*
* Arrange for var1 to be the number with the greater absolute value.
*
* This would happen automatically in the loop below, but avoids an
* expensive modulo operation.
*/
cmp = cmp_abs(var1, var2);
if (cmp < 0)
{
const NumericVar *tmp = var1;
var1 = var2;
var2 = tmp;
}
/*
* Also avoid the taking the modulo if the inputs have the same absolute
* value, or if the smaller input is zero.
*/
if (cmp == 0 || var2->ndigits == 0)
{
set_var_from_var(var1, result);
result->sign = NUMERIC_POS;
result->dscale = res_dscale;
return;
}
init_var(&tmp_arg);
init_var(&mod);
/* Use the Euclidean algorithm to find the GCD */
set_var_from_var(var1, &tmp_arg);
set_var_from_var(var2, result);
for (;;)
{
/* this loop can take a while, so allow it to be interrupted */
CHECK_FOR_INTERRUPTS();
mod_var(&tmp_arg, result, &mod);
if (mod.ndigits == 0)
break;
set_var_from_var(result, &tmp_arg);
set_var_from_var(&mod, result);
}
result->sign = NUMERIC_POS;
result->dscale = res_dscale;
free_var(&tmp_arg);
free_var(&mod);
}
/*
* sqrt_var() -
*