diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6c4359dc7be..895b4b7b1b6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -870,6 +870,40 @@
-43
+
+
+
+ gcd
+
+ gcd(a, b)
+
+ (same as argument types)
+
+ greatest common divisor (the largest positive number that divides both
+ inputs with no remainder); returns 0 if both inputs
+ are zero
+
+ gcd(1071, 462)
+ 21
+
+
+
+
+
+ lcm
+
+ lcm(a, b)
+
+ (same as argument types)
+
+ least common multiple (the smallest strictly positive number that is
+ an integral multiple of both inputs); returns 0 if
+ either input is zero
+
+ lcm(1071, 462)
+ 23562
+
+
diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c
index 583ce71e664..4acbc27d426 100644
--- a/src/backend/utils/adt/int.c
+++ b/src/backend/utils/adt/int.c
@@ -1196,6 +1196,132 @@ int2abs(PG_FUNCTION_ARGS)
PG_RETURN_INT16(result);
}
+/*
+ * Greatest Common Divisor
+ *
+ * Returns the largest positive integer that exactly divides both inputs.
+ * Special cases:
+ * - gcd(x, 0) = gcd(0, x) = abs(x)
+ * because 0 is divisible by anything
+ * - gcd(0, 0) = 0
+ * complies with the previous definition and is a common convention
+ *
+ * Special care must be taken if either input is INT_MIN --- gcd(0, INT_MIN),
+ * gcd(INT_MIN, 0) and gcd(INT_MIN, INT_MIN) are all equal to abs(INT_MIN),
+ * which cannot be represented as a 32-bit signed integer.
+ */
+static int32
+int4gcd_internal(int32 arg1, int32 arg2)
+{
+ int32 swap;
+ int32 a1, a2;
+
+ /*
+ * Put the greater absolute value in arg1.
+ *
+ * This would happen automatically in the loop below, but avoids an
+ * expensive modulo operation, and simplifies the special-case handling
+ * for INT_MIN below.
+ *
+ * We do this in negative space in order to handle INT_MIN.
+ */
+ a1 = (arg1 < 0) ? arg1 : -arg1;
+ a2 = (arg2 < 0) ? arg2 : -arg2;
+ if (a1 > a2)
+ {
+ swap = arg1;
+ arg1 = arg2;
+ arg2 = swap;
+ }
+
+ /* Special care needs to be taken with INT_MIN. See comments above. */
+ if (arg1 == PG_INT32_MIN)
+ {
+ if (arg2 == 0 || arg2 == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("integer out of range")));
+
+ /*
+ * Some machines throw a floating-point exception for INT_MIN % -1,
+ * which is a bit silly since the correct answer is perfectly
+ * well-defined, namely zero. Guard against this and just return the
+ * result, gcd(INT_MIN, -1) = 1.
+ */
+ if (arg2 == -1)
+ return 1;
+ }
+
+ /* Use the Euclidean algorithm to find the GCD */
+ while (arg2 != 0)
+ {
+ swap = arg2;
+ arg2 = arg1 % arg2;
+ arg1 = swap;
+ }
+
+ /*
+ * Make sure the result is positive. (We know we don't have INT_MIN
+ * anymore).
+ */
+ if (arg1 < 0)
+ arg1 = -arg1;
+
+ return arg1;
+}
+
+Datum
+int4gcd(PG_FUNCTION_ARGS)
+{
+ int32 arg1 = PG_GETARG_INT32(0);
+ int32 arg2 = PG_GETARG_INT32(1);
+ int32 result;
+
+ result = int4gcd_internal(arg1, arg2);
+
+ PG_RETURN_INT32(result);
+}
+
+/*
+ * Least Common Multiple
+ */
+Datum
+int4lcm(PG_FUNCTION_ARGS)
+{
+ int32 arg1 = PG_GETARG_INT32(0);
+ int32 arg2 = PG_GETARG_INT32(1);
+ int32 gcd;
+ int32 result;
+
+ /*
+ * Handle lcm(x, 0) = lcm(0, x) = 0 as a special case. This prevents a
+ * division-by-zero error below when x is zero, and an overflow error from
+ * the GCD computation when x = INT_MIN.
+ */
+ if (arg1 == 0 || arg2 == 0)
+ PG_RETURN_INT32(0);
+
+ /* lcm(x, y) = abs(x / gcd(x, y) * y) */
+ gcd = int4gcd_internal(arg1, arg2);
+ arg1 = arg1 / gcd;
+
+ if (unlikely(pg_mul_s32_overflow(arg1, arg2, &result)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("integer out of range")));
+
+ /* If the result is INT_MIN, it cannot be represented. */
+ if (unlikely(result == PG_INT32_MIN))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("integer out of range")));
+
+ if (result < 0)
+ result = -result;
+
+ PG_RETURN_INT32(result);
+}
+
Datum
int2larger(PG_FUNCTION_ARGS)
{
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index fcdf77331e7..494768c1901 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -667,6 +667,132 @@ int8mod(PG_FUNCTION_ARGS)
PG_RETURN_INT64(arg1 % arg2);
}
+/*
+ * Greatest Common Divisor
+ *
+ * Returns the largest positive integer that exactly divides both inputs.
+ * Special cases:
+ * - gcd(x, 0) = gcd(0, x) = abs(x)
+ * because 0 is divisible by anything
+ * - gcd(0, 0) = 0
+ * complies with the previous definition and is a common convention
+ *
+ * Special care must be taken if either input is INT64_MIN ---
+ * gcd(0, INT64_MIN), gcd(INT64_MIN, 0) and gcd(INT64_MIN, INT64_MIN) are
+ * all equal to abs(INT64_MIN), which cannot be represented as a 64-bit signed
+ * integer.
+ */
+static int64
+int8gcd_internal(int64 arg1, int64 arg2)
+{
+ int64 swap;
+ int64 a1, a2;
+
+ /*
+ * Put the greater absolute value in arg1.
+ *
+ * This would happen automatically in the loop below, but avoids an
+ * expensive modulo operation, and simplifies the special-case handling
+ * for INT64_MIN below.
+ *
+ * We do this in negative space in order to handle INT64_MIN.
+ */
+ a1 = (arg1 < 0) ? arg1 : -arg1;
+ a2 = (arg2 < 0) ? arg2 : -arg2;
+ if (a1 > a2)
+ {
+ swap = arg1;
+ arg1 = arg2;
+ arg2 = swap;
+ }
+
+ /* Special care needs to be taken with INT64_MIN. See comments above. */
+ if (arg1 == PG_INT64_MIN)
+ {
+ if (arg2 == 0 || arg2 == PG_INT64_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("bigint out of range")));
+
+ /*
+ * Some machines throw a floating-point exception for INT64_MIN % -1,
+ * which is a bit silly since the correct answer is perfectly
+ * well-defined, namely zero. Guard against this and just return the
+ * result, gcd(INT64_MIN, -1) = 1.
+ */
+ if (arg2 == -1)
+ return 1;
+ }
+
+ /* Use the Euclidean algorithm to find the GCD */
+ while (arg2 != 0)
+ {
+ swap = arg2;
+ arg2 = arg1 % arg2;
+ arg1 = swap;
+ }
+
+ /*
+ * Make sure the result is positive. (We know we don't have INT64_MIN
+ * anymore).
+ */
+ if (arg1 < 0)
+ arg1 = -arg1;
+
+ return arg1;
+}
+
+Datum
+int8gcd(PG_FUNCTION_ARGS)
+{
+ int64 arg1 = PG_GETARG_INT64(0);
+ int64 arg2 = PG_GETARG_INT64(1);
+ int64 result;
+
+ result = int8gcd_internal(arg1, arg2);
+
+ PG_RETURN_INT64(result);
+}
+
+/*
+ * Least Common Multiple
+ */
+Datum
+int8lcm(PG_FUNCTION_ARGS)
+{
+ int64 arg1 = PG_GETARG_INT64(0);
+ int64 arg2 = PG_GETARG_INT64(1);
+ int64 gcd;
+ int64 result;
+
+ /*
+ * Handle lcm(x, 0) = lcm(0, x) = 0 as a special case. This prevents a
+ * division-by-zero error below when x is zero, and an overflow error from
+ * the GCD computation when x = INT64_MIN.
+ */
+ if (arg1 == 0 || arg2 == 0)
+ PG_RETURN_INT64(0);
+
+ /* lcm(x, y) = abs(x / gcd(x, y) * y) */
+ gcd = int8gcd_internal(arg1, arg2);
+ arg1 = arg1 / gcd;
+
+ if (unlikely(pg_mul_s64_overflow(arg1, arg2, &result)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("bigint out of range")));
+
+ /* If the result is INT64_MIN, it cannot be represented. */
+ if (unlikely(result == PG_INT64_MIN))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("bigint out of range")));
+
+ if (result < 0)
+ result = -result;
+
+ PG_RETURN_INT64(result);
+}
Datum
int8inc(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 76a597e56fa..c92ad5a4fe0 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -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() -
*
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index e05494a857f..249b1f5a341 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202001171
+#define CATALOG_VERSION_NO 202001251
#endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a1214c4..bef50c76d9c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1818,6 +1818,20 @@
proname => 'mod', prorettype => 'int8', proargtypes => 'int8 int8',
prosrc => 'int8mod' },
+{ oid => '8463', descr => 'greatest common divisor',
+ proname => 'gcd', prorettype => 'int4', proargtypes => 'int4 int4',
+ prosrc => 'int4gcd' },
+{ oid => '8464', descr => 'greatest common divisor',
+ proname => 'gcd', prorettype => 'int8', proargtypes => 'int8 int8',
+ prosrc => 'int8gcd' },
+
+{ oid => '8465', descr => 'least common multiple',
+ proname => 'lcm', prorettype => 'int4', proargtypes => 'int4 int4',
+ prosrc => 'int4lcm' },
+{ oid => '8466', descr => 'least common multiple',
+ proname => 'lcm', prorettype => 'int8', proargtypes => 'int8 int8',
+ prosrc => 'int8lcm' },
+
{ oid => '944', descr => 'convert text to char',
proname => 'char', prorettype => 'char', proargtypes => 'text',
prosrc => 'text_char' },
@@ -4218,6 +4232,12 @@
{ oid => '1729',
proname => 'numeric_mod', prorettype => 'numeric',
proargtypes => 'numeric numeric', prosrc => 'numeric_mod' },
+{ oid => '8467', descr => 'greatest common divisor',
+ proname => 'gcd', prorettype => 'numeric', proargtypes => 'numeric numeric',
+ prosrc => 'numeric_gcd' },
+{ oid => '8468', descr => 'least common multiple',
+ proname => 'lcm', prorettype => 'numeric', proargtypes => 'numeric numeric',
+ prosrc => 'numeric_lcm' },
{ oid => '1730', descr => 'square root',
proname => 'sqrt', prorettype => 'numeric', proargtypes => 'numeric',
prosrc => 'numeric_sqrt' },
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index bda7a8daefc..c384af18ee8 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -403,3 +403,49 @@ FROM (VALUES (-2.5::numeric),
2.5 | 3
(7 rows)
+-- test gcd()
+SELECT a, b, gcd(a, b), gcd(a, -b), gcd(b, a), gcd(-b, a)
+FROM (VALUES (0::int4, 0::int4),
+ (0::int4, 6410818::int4),
+ (61866666::int4, 6410818::int4),
+ (-61866666::int4, 6410818::int4),
+ ((-2147483648)::int4, 1::int4),
+ ((-2147483648)::int4, 2147483647::int4),
+ ((-2147483648)::int4, 1073741824::int4)) AS v(a, b);
+ a | b | gcd | gcd | gcd | gcd
+-------------+------------+------------+------------+------------+------------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | 6410818 | 6410818 | 6410818 | 6410818 | 6410818
+ 61866666 | 6410818 | 1466 | 1466 | 1466 | 1466
+ -61866666 | 6410818 | 1466 | 1466 | 1466 | 1466
+ -2147483648 | 1 | 1 | 1 | 1 | 1
+ -2147483648 | 2147483647 | 1 | 1 | 1 | 1
+ -2147483648 | 1073741824 | 1073741824 | 1073741824 | 1073741824 | 1073741824
+(7 rows)
+
+SELECT gcd((-2147483648)::int4, 0::int4); -- overflow
+ERROR: integer out of range
+SELECT gcd((-2147483648)::int4, (-2147483648)::int4); -- overflow
+ERROR: integer out of range
+-- test lcm()
+SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a)
+FROM (VALUES (0::int4, 0::int4),
+ (0::int4, 42::int4),
+ (42::int4, 42::int4),
+ (330::int4, 462::int4),
+ (-330::int4, 462::int4),
+ ((-2147483648)::int4, 0::int4)) AS v(a, b);
+ a | b | lcm | lcm | lcm | lcm
+-------------+-----+------+------+------+------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | 42 | 0 | 0 | 0 | 0
+ 42 | 42 | 42 | 42 | 42 | 42
+ 330 | 462 | 2310 | 2310 | 2310 | 2310
+ -330 | 462 | 2310 | 2310 | 2310 | 2310
+ -2147483648 | 0 | 0 | 0 | 0 | 0
+(6 rows)
+
+SELECT lcm((-2147483648)::int4, 1::int4); -- overflow
+ERROR: integer out of range
+SELECT lcm(2147483647::int4, 2147483646::int4); -- overflow
+ERROR: integer out of range
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 8447a28c3d3..813e3a82866 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -886,3 +886,49 @@ FROM (VALUES (-2.5::numeric),
2.5 | 3
(7 rows)
+-- test gcd()
+SELECT a, b, gcd(a, b), gcd(a, -b), gcd(b, a), gcd(-b, a)
+FROM (VALUES (0::int8, 0::int8),
+ (0::int8, 29893644334::int8),
+ (288484263558::int8, 29893644334::int8),
+ (-288484263558::int8, 29893644334::int8),
+ ((-9223372036854775808)::int8, 1::int8),
+ ((-9223372036854775808)::int8, 9223372036854775807::int8),
+ ((-9223372036854775808)::int8, 4611686018427387904::int8)) AS v(a, b);
+ a | b | gcd | gcd | gcd | gcd
+----------------------+---------------------+---------------------+---------------------+---------------------+---------------------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | 29893644334 | 29893644334 | 29893644334 | 29893644334 | 29893644334
+ 288484263558 | 29893644334 | 6835958 | 6835958 | 6835958 | 6835958
+ -288484263558 | 29893644334 | 6835958 | 6835958 | 6835958 | 6835958
+ -9223372036854775808 | 1 | 1 | 1 | 1 | 1
+ -9223372036854775808 | 9223372036854775807 | 1 | 1 | 1 | 1
+ -9223372036854775808 | 4611686018427387904 | 4611686018427387904 | 4611686018427387904 | 4611686018427387904 | 4611686018427387904
+(7 rows)
+
+SELECT gcd((-9223372036854775808)::int8, 0::int8); -- overflow
+ERROR: bigint out of range
+SELECT gcd((-9223372036854775808)::int8, (-9223372036854775808)::int8); -- overflow
+ERROR: bigint out of range
+-- test lcm()
+SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a)
+FROM (VALUES (0::int8, 0::int8),
+ (0::int8, 29893644334::int8),
+ (29893644334::int8, 29893644334::int8),
+ (288484263558::int8, 29893644334::int8),
+ (-288484263558::int8, 29893644334::int8),
+ ((-9223372036854775808)::int8, 0::int8)) AS v(a, b);
+ a | b | lcm | lcm | lcm | lcm
+----------------------+-------------+------------------+------------------+------------------+------------------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | 29893644334 | 0 | 0 | 0 | 0
+ 29893644334 | 29893644334 | 29893644334 | 29893644334 | 29893644334 | 29893644334
+ 288484263558 | 29893644334 | 1261541684539134 | 1261541684539134 | 1261541684539134 | 1261541684539134
+ -288484263558 | 29893644334 | 1261541684539134 | 1261541684539134 | 1261541684539134 | 1261541684539134
+ -9223372036854775808 | 0 | 0 | 0 | 0 | 0
+(6 rows)
+
+SELECT lcm((-9223372036854775808)::int8, 1::int8); -- overflow
+ERROR: bigint out of range
+SELECT lcm(9223372036854775807::int8, 9223372036854775806::int8); -- overflow
+ERROR: bigint out of range
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 8acfa394245..23a4c6dcc3f 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2220,3 +2220,47 @@ SELECT SUM((-9999)::numeric) FROM generate_series(1, 100000);
-999900000
(1 row)
+--
+-- Tests for GCD()
+--
+SELECT a, b, gcd(a, b), gcd(a, -b), gcd(-b, a), gcd(-b, -a)
+FROM (VALUES (0::numeric, 0::numeric),
+ (0::numeric, numeric 'NaN'),
+ (0::numeric, 46375::numeric),
+ (433125::numeric, 46375::numeric),
+ (43312.5::numeric, 4637.5::numeric),
+ (4331.250::numeric, 463.75000::numeric)) AS v(a, b);
+ a | b | gcd | gcd | gcd | gcd
+----------+-----------+---------+---------+---------+---------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | NaN | NaN | NaN | NaN | NaN
+ 0 | 46375 | 46375 | 46375 | 46375 | 46375
+ 433125 | 46375 | 875 | 875 | 875 | 875
+ 43312.5 | 4637.5 | 87.5 | 87.5 | 87.5 | 87.5
+ 4331.250 | 463.75000 | 8.75000 | 8.75000 | 8.75000 | 8.75000
+(6 rows)
+
+--
+-- Tests for LCM()
+--
+SELECT a,b, lcm(a, b), lcm(a, -b), lcm(-b, a), lcm(-b, -a)
+FROM (VALUES (0::numeric, 0::numeric),
+ (0::numeric, numeric 'NaN'),
+ (0::numeric, 13272::numeric),
+ (13272::numeric, 13272::numeric),
+ (423282::numeric, 13272::numeric),
+ (42328.2::numeric, 1327.2::numeric),
+ (4232.820::numeric, 132.72000::numeric)) AS v(a, b);
+ a | b | lcm | lcm | lcm | lcm
+----------+-----------+--------------+--------------+--------------+--------------
+ 0 | 0 | 0 | 0 | 0 | 0
+ 0 | NaN | NaN | NaN | NaN | NaN
+ 0 | 13272 | 0 | 0 | 0 | 0
+ 13272 | 13272 | 13272 | 13272 | 13272 | 13272
+ 423282 | 13272 | 11851896 | 11851896 | 11851896 | 11851896
+ 42328.2 | 1327.2 | 1185189.6 | 1185189.6 | 1185189.6 | 1185189.6
+ 4232.820 | 132.72000 | 118518.96000 | 118518.96000 | 118518.96000 | 118518.96000
+(7 rows)
+
+SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow
+ERROR: value overflows numeric format
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index f014cb2d325..a9e90a96c4c 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -155,3 +155,28 @@ FROM (VALUES (-2.5::numeric),
(0.5::numeric),
(1.5::numeric),
(2.5::numeric)) t(x);
+
+-- test gcd()
+SELECT a, b, gcd(a, b), gcd(a, -b), gcd(b, a), gcd(-b, a)
+FROM (VALUES (0::int4, 0::int4),
+ (0::int4, 6410818::int4),
+ (61866666::int4, 6410818::int4),
+ (-61866666::int4, 6410818::int4),
+ ((-2147483648)::int4, 1::int4),
+ ((-2147483648)::int4, 2147483647::int4),
+ ((-2147483648)::int4, 1073741824::int4)) AS v(a, b);
+
+SELECT gcd((-2147483648)::int4, 0::int4); -- overflow
+SELECT gcd((-2147483648)::int4, (-2147483648)::int4); -- overflow
+
+-- test lcm()
+SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a)
+FROM (VALUES (0::int4, 0::int4),
+ (0::int4, 42::int4),
+ (42::int4, 42::int4),
+ (330::int4, 462::int4),
+ (-330::int4, 462::int4),
+ ((-2147483648)::int4, 0::int4)) AS v(a, b);
+
+SELECT lcm((-2147483648)::int4, 1::int4); -- overflow
+SELECT lcm(2147483647::int4, 2147483646::int4); -- overflow
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index e890452236f..dba3ade687a 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -225,3 +225,28 @@ FROM (VALUES (-2.5::numeric),
(0.5::numeric),
(1.5::numeric),
(2.5::numeric)) t(x);
+
+-- test gcd()
+SELECT a, b, gcd(a, b), gcd(a, -b), gcd(b, a), gcd(-b, a)
+FROM (VALUES (0::int8, 0::int8),
+ (0::int8, 29893644334::int8),
+ (288484263558::int8, 29893644334::int8),
+ (-288484263558::int8, 29893644334::int8),
+ ((-9223372036854775808)::int8, 1::int8),
+ ((-9223372036854775808)::int8, 9223372036854775807::int8),
+ ((-9223372036854775808)::int8, 4611686018427387904::int8)) AS v(a, b);
+
+SELECT gcd((-9223372036854775808)::int8, 0::int8); -- overflow
+SELECT gcd((-9223372036854775808)::int8, (-9223372036854775808)::int8); -- overflow
+
+-- test lcm()
+SELECT a, b, lcm(a, b), lcm(a, -b), lcm(b, a), lcm(-b, a)
+FROM (VALUES (0::int8, 0::int8),
+ (0::int8, 29893644334::int8),
+ (29893644334::int8, 29893644334::int8),
+ (288484263558::int8, 29893644334::int8),
+ (-288484263558::int8, 29893644334::int8),
+ ((-9223372036854775808)::int8, 0::int8)) AS v(a, b);
+
+SELECT lcm((-9223372036854775808)::int8, 1::int8); -- overflow
+SELECT lcm(9223372036854775807::int8, 9223372036854775806::int8); -- overflow
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index e611cc4d8dc..c5c8d76727d 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1073,3 +1073,28 @@ select trim_scale(1e100);
-- cases that need carry propagation
SELECT SUM(9999::numeric) FROM generate_series(1, 100000);
SELECT SUM((-9999)::numeric) FROM generate_series(1, 100000);
+
+--
+-- Tests for GCD()
+--
+SELECT a, b, gcd(a, b), gcd(a, -b), gcd(-b, a), gcd(-b, -a)
+FROM (VALUES (0::numeric, 0::numeric),
+ (0::numeric, numeric 'NaN'),
+ (0::numeric, 46375::numeric),
+ (433125::numeric, 46375::numeric),
+ (43312.5::numeric, 4637.5::numeric),
+ (4331.250::numeric, 463.75000::numeric)) AS v(a, b);
+
+--
+-- Tests for LCM()
+--
+SELECT a,b, lcm(a, b), lcm(a, -b), lcm(-b, a), lcm(-b, -a)
+FROM (VALUES (0::numeric, 0::numeric),
+ (0::numeric, numeric 'NaN'),
+ (0::numeric, 13272::numeric),
+ (13272::numeric, 13272::numeric),
+ (423282::numeric, 13272::numeric),
+ (42328.2::numeric, 1327.2::numeric),
+ (4232.820::numeric, 132.72000::numeric)) AS v(a, b);
+
+SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow