From 13661ddd7eaec7e2809ff5c29fc14653b6161036 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Sat, 25 Jan 2020 14:00:59 +0000 Subject: [PATCH] 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 --- doc/src/sgml/func.sgml | 34 +++++ src/backend/utils/adt/int.c | 126 +++++++++++++++++++ src/backend/utils/adt/int8.c | 126 +++++++++++++++++++ src/backend/utils/adt/numeric.c | 171 ++++++++++++++++++++++++++ src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.dat | 20 +++ src/test/regress/expected/int4.out | 46 +++++++ src/test/regress/expected/int8.out | 46 +++++++ src/test/regress/expected/numeric.out | 44 +++++++ src/test/regress/sql/int4.sql | 25 ++++ src/test/regress/sql/int8.sql | 25 ++++ src/test/regress/sql/numeric.sql | 25 ++++ 12 files changed, 689 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 6c4359dc7b..895b4b7b1b 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 583ce71e66..4acbc27d42 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 fcdf77331e..494768c190 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 76a597e56f..c92ad5a4fe 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 e05494a857..249b1f5a34 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 fcf2a1214c..bef50c76d9 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 bda7a8daef..c384af18ee 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 8447a28c3d..813e3a8286 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 8acfa39424..23a4c6dcc3 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 f014cb2d32..a9e90a96c4 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 e890452236..dba3ade687 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 e611cc4d8d..c5c8d76727 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