Add functions gcd() and lcm() for integer and numeric types.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 25 Jan 2020 14:00:59 +0000 (14:00 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Sat, 25 Jan 2020 14:00:59 +0000 (14:00 +0000)
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

12 files changed:
doc/src/sgml/func.sgml
src/backend/utils/adt/int.c
src/backend/utils/adt/int8.c
src/backend/utils/adt/numeric.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/test/regress/expected/int4.out
src/test/regress/expected/int8.out
src/test/regress/expected/numeric.out
src/test/regress/sql/int4.sql
src/test/regress/sql/int8.sql
src/test/regress/sql/numeric.sql

index 6c4359dc7beccdda3053890cd5d6fb0a71eca257..895b4b7b1b670eb37efcf9edf9bbdc971646b455 100644 (file)
        <entry><literal>-43</literal></entry>
       </row>
 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>gcd</primary>
+        </indexterm>
+        <literal><function>gcd(<replaceable>a</replaceable>, <replaceable>b</replaceable>)</function></literal>
+       </entry>
+       <entry>(same as argument types)</entry>
+       <entry>
+        greatest common divisor (the largest positive number that divides both
+        inputs with no remainder); returns <literal>0</literal> if both inputs
+        are zero
+       </entry>
+       <entry><literal>gcd(1071, 462)</literal></entry>
+       <entry><literal>21</literal></entry>
+      </row>
+
+      <row>
+       <entry>
+        <indexterm>
+         <primary>lcm</primary>
+        </indexterm>
+        <literal><function>lcm(<replaceable>a</replaceable>, <replaceable>b</replaceable>)</function></literal>
+       </entry>
+       <entry>(same as argument types)</entry>
+       <entry>
+        least common multiple (the smallest strictly positive number that is
+        an integral multiple of both inputs); returns <literal>0</literal> if
+        either input is zero
+       </entry>
+       <entry><literal>lcm(1071, 462)</literal></entry>
+       <entry><literal>23562</literal></entry>
+      </row>
+
       <row>
        <entry>
         <indexterm>
index 583ce71e664d8a8c924e89f6f188aa919f234777..4acbc27d426e846f51091ebc09d40d2b5161be2b 100644 (file)
@@ -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)
 {
index fcdf77331e73e6e0541fa4cfc1d22e3e118102a6..494768c190151de90303a6fe6f66c2329d0d4d06 100644 (file)
@@ -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)
index 76a597e56fa4dad0995ddaeb5ca87d31126f7498..c92ad5a4fe06318df431bfefae5a35e0fa313464 100644 (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() -
  *
index e05494a857f3bbb66a06d66d943c2bd13c2d72eb..249b1f5a3419a7a8dc64d975a29dfd9337979f73 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202001171
+#define CATALOG_VERSION_NO 202001251
 
 #endif
index fcf2a1214c4388392c9eb439cca3c565c35f20fd..bef50c76d9c5de02a33729ed0e3e4fb7c365b2ed 100644 (file)
   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' },
 { 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' },
index bda7a8daefc62f27d9104c75be5d85271708b08e..c384af18ee89dee5f0dd16ed166cea97aac90e0d 100644 (file)
@@ -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
index 8447a28c3d3c0a51664759f49c49b9e441cce9d4..813e3a828663526ec739d2e9a5111640052da89f 100644 (file)
@@ -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
index 8acfa3942451d315f03a1a33f84b19147aa3838d..23a4c6dcc3f402b7f6b2628667168911b261f3e7 100644 (file)
@@ -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
index f014cb2d3250a0b2c347f215545b474f59ca8b41..a9e90a96c4c092eb22770474af75bddc08ca4686 100644 (file)
@@ -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
index e890452236f95d41715c5e5b17b8b49c1fe32de2..dba3ade687ac422e0683d3a3ab9ef05cf6bcbfba 100644 (file)
@@ -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
index e611cc4d8dc3b6eb3fba43b63ed2e1958fd58d2f..c5c8d76727d60d29d4953599065365a9f0b243a2 100644 (file)
@@ -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