Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1ilZ63-00067a-E2 for pgsql-hackers@arkaria.postgresql.org; Sun, 29 Dec 2019 13:59:07 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1ilZ61-0007nE-RZ for pgsql-hackers@arkaria.postgresql.org; Sun, 29 Dec 2019 13:59:05 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1ilZ61-0007n6-AD for pgsql-hackers@lists.postgresql.org; Sun, 29 Dec 2019 13:59:05 +0000 Received: from mail-wr1-x432.google.com ([2a00:1450:4864:20::432]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1ilZ5r-0007ZM-Mg for pgsql-hackers@lists.postgresql.org; Sun, 29 Dec 2019 13:59:01 +0000 Received: by mail-wr1-x432.google.com with SMTP id y17so30547571wrh.5 for ; Sun, 29 Dec 2019 05:58:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=subject:to:cc:references:from:message-id:date:user-agent :mime-version:in-reply-to:content-language; bh=UBGyVk2uJNMAxlTK81gZ3ZoEyA94YH+Ul+CvkWEpxiY=; b=t2ZuDrmu4zl2UTrg1EwciWeoMPwMws4xibqg13I/lNy8Usunr0IWwVY5FCnVbkr7O3 kMbhi2ObWaLWpUU928OJMbflTbvt92+2yci//+LU3pMcfzTSPYoCuPElDu53qn4tqUEj TismIffdmSFVrqgcYG3W9TI7wDsrlTzAeVCR8DL5mQnZLOHjUM6afH0VOXBzaGmTLObq H91NmsdLSdQMUAaloBKPgEj0z1ie8aMMvDNiCkPzbhtCU0zAFpRHTO//oSpOxFOnzKDI L9gl6ywze6a/KAPo84QYZsQxhzTuA6e36nc4zrGAtj6wfEHtEROcDDIIDQBY2sJtVfQR qo4A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:to:cc:references:from:message-id:date :user-agent:mime-version:in-reply-to:content-language; bh=UBGyVk2uJNMAxlTK81gZ3ZoEyA94YH+Ul+CvkWEpxiY=; b=TgQ2R0aqXW/gml3SRJh2tYzmCt9b4JE7Ut9G+mHhByV6iN/EpHw8qu1OKDjuuVjYeN cPtfOGBJVRIVle7t19W4SYxGPvEepMXdydFN39gzzn/xs1xYl+sZwnm0p9+keP5/Le0Q sgbfKBXnnqf7raDifV++LTXXcIH4uNfcRXwimyJoprn90b3fNUmNsl6MHU3QUiH90bky 09PdR1kxjSvSYMoOIE3v3WaWrqI9TLJryGz8aLsAErwphUHLG3hxq4JDM9awShy0UF7f LHit3Nbp8b8+KGaCj94ZCGHPzGJfxPRNMk1eYYqVcKcxz9CkHGCucd8tC59OCn2ueQeC 5LOQ== X-Gm-Message-State: APjAAAVZvb8D7NydHKgv7uCPXvvHZGb6Pa4Qik8rpcM1aOqrwrnDfJdC Vun1kwQWsqF0SiSsnzIGMnpN5DT46o7swhNwSCcAGWERZqN/ULb1+9NR4il1clXV1w9wwpTdOOT JJSlM1maqmW82wTqtk4fIIqPU+dKkqk09F31PiwOaeQcSjIPeyOBtqKkbq//bU/bWv062Kpputd AwLIuqPdF3aWFNPBW9QyOt X-Google-Smtp-Source: APXvYqw92JSHofO6JgVDjLjea8gFdP6krGqbCiosS3QSsv4mO6qJhxE8U3sONxlidFJvmO7ZJJ6Emg== X-Received: by 2002:a5d:4392:: with SMTP id i18mr64198372wrq.199.1577627932641; Sun, 29 Dec 2019 05:58:52 -0800 (PST) Received: from [10.8.8.7] ([185.94.189.189]) by smtp.gmail.com with ESMTPSA id c17sm42506166wrr.87.2019.12.29.05.58.51 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Sun, 29 Dec 2019 05:58:52 -0800 (PST) Subject: Re: Greatest Common Divisor To: Fabien COELHO Cc: PostgreSQL Hackers References: From: Vik Fearing Message-ID: <9890a02a-94d4-537c-9f4a-ff02a5414720@2ndquadrant.com> Date: Sun, 29 Dec 2019 14:58:49 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.2.2 MIME-Version: 1.0 In-Reply-To: Content-Type: multipart/mixed; boundary="------------A07F80366BA3A32A5E612C67" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------A07F80366BA3A32A5E612C67 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit On 29/12/2019 08:30, Fabien COELHO wrote: > >>> I'm wondering what it should do on N, 0 and 0, 0. Raise an error? >>> Return 0? Return 1? return N? There should be some logic and comments >>> explaining it. >> >> Well, gcd(N, 0) is N, and gcd(0, 0) is 0, so I don't see an issue here? > > I think that there should be a comment. Done. >>> It does not seem that fixing the sign afterwards is the right thing to >>> do. I'd rather turn both arguments positive before doing the descent. >> >> Why isn't it the right thing to do? > > Because I do not trust C modulo as I had a lot of problems with it? :-) > > If it works, but it should deserve a clear comment explaining why. Surely such a comment should be on the mod functions and not in this patch. > >>> Which raises an issue with INT_MIN by the way, which has no positive:-( >> >> That's an argument against abs-ing the input values.  It's only an issue >> with gcd(INT_MIN, INT_MIN) which currently returns INT_MIN. > > That should be an error instead, because -INT_MIN cannot be represented? Why should it error?  Is INT_MIN not a valid divisor of INT_MIN?  I added a comment instead. >>> Also, the usual approach is to exchange args so that the largest is >>> first, because there may be a software emulation if the hardware does >>> not implement modulo. At least it was the case with some sparc >>> processors 25 years ago:-) >> >> The args will exchange themselves. > > Yep, but after a possibly expensive software-emulated modulo operation? > I'll just trust you on this.  Swap added. Thanks! -- Vik --------------A07F80366BA3A32A5E612C67 Content-Type: text/x-patch; charset=UTF-8; name="gcd.0003.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="gcd.0003.patch" diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 57a1539506..b2c663cd92 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -870,6 +870,19 @@ -43 + + + + gcd + + gcd(a, b) + + (same as argument types) + greatest common divisor + gcd(1071, 462) + 21 + + diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index 04825fc77d..61b50db71c 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -1196,6 +1196,76 @@ int2abs(PG_FUNCTION_ARGS) PG_RETURN_INT16(result); } +/* int[24]gcd() + * Greatest Common Divisor + * + * By definition, gcd(0, 0) = 0 and gcd(x, 0) = x. + */ +Datum +int4gcd(PG_FUNCTION_ARGS) +{ + int32 arg1 = PG_GETARG_INT32(0); + int32 arg2 = PG_GETARG_INT32(1); + int32 swap; + + /* + * Put the greater value in arg1. + * This would happen automatically in the loop below, but avoids an + * expensive modulo simulation on some architectures. + */ + if (arg1 < arg2) + { + swap = arg1; + arg1 = arg2; + arg2 = swap; + } + + /* Find GCD using the basic Euclidean algorithm */ + while (arg2 != 0) + { + swap = arg2; + arg2 = arg1 % arg2; + arg1 = swap; + } + + /* + * Make sure the result is positive. (INT_MIN will still be negative + * because there is no positive representation of it). + */ + if (arg1 < 0) + arg1 = -arg1; + + PG_RETURN_INT32(arg1); +} + +Datum +int2gcd(PG_FUNCTION_ARGS) +{ + /* See int4gcd for commented version. */ + int16 arg1 = PG_GETARG_INT16(0); + int16 arg2 = PG_GETARG_INT16(1); + int16 swap; + + if (arg1 < arg2) + { + swap = arg1; + arg1 = arg2; + arg2 = swap; + } + + while (arg2 != 0) + { + swap = arg2; + arg2 = arg1 % arg2; + arg1 = swap; + } + + if (arg1 < 0) + arg1 = -arg1; + + PG_RETURN_INT16(arg1); +} + Datum int2larger(PG_FUNCTION_ARGS) { diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index a40ae40dff..59be1534a4 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -667,6 +667,38 @@ int8mod(PG_FUNCTION_ARGS) PG_RETURN_INT64(arg1 % arg2); } +/* int8gcd() + * Greatest Common Divisor + * + * See int4gcd for commented version. + */ +Datum +int8gcd(PG_FUNCTION_ARGS) +{ + int64 arg1 = PG_GETARG_INT64(0); + int64 arg2 = PG_GETARG_INT64(1); + int64 swap; + + if (arg1 < arg2) + { + swap = arg1; + arg1 = arg2; + arg2 = swap; + } + + while (arg2 != 0) + { + swap = arg2; + arg2 = arg1 % arg2; + arg1 = swap; + } + + if (arg1 < 0) + arg1 = -arg1; + + PG_RETURN_INT64(arg1); +} + Datum int8inc(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index ac8f64b219..b3057f2cdd 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -10729,4 +10729,15 @@ proname => 'pg_partition_root', prorettype => 'regclass', proargtypes => 'regclass', prosrc => 'pg_partition_root' }, +# greatest common divisor +{ oid => '8463', descr => 'greatest common divisor', + proname => 'gcd', prorettype => 'int2', proargtypes => 'int2 int2', + prosrc => 'int2gcd' }, +{ oid => '8464', descr => 'greatest common divisor', + proname => 'gcd', prorettype => 'int4', proargtypes => 'int4 int4', + prosrc => 'int4gcd' }, +{ oid => '8465', descr => 'greatest common divisor', + proname => 'gcd', prorettype => 'int8', proargtypes => 'int8 int8', + prosrc => 'int8gcd' }, + ] diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out index 8c255b9e4d..cca47af7fb 100644 --- a/src/test/regress/expected/int2.out +++ b/src/test/regress/expected/int2.out @@ -306,3 +306,23 @@ FROM (VALUES (-2.5::numeric), 2.5 | 3 (7 rows) +-- gcd +SELECT gcd(a, b), gcd(a, -b), gcd(-a, b), gcd(-a, -b), gcd(b, a) +FROM (VALUES (24948::int2, 4914::int2)) AS v(a, b); + gcd | gcd | gcd | gcd | gcd +-----+-----+-----+-----+----- + 378 | 378 | 378 | 378 | 378 +(1 row) + +SELECT gcd((-32768)::int2, 16384::int2); + gcd +------- + 16384 +(1 row) + +SELECT gcd((-32768)::int2, (-32768)::int2); + gcd +-------- + -32768 +(1 row) + diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out index bda7a8daef..3f0f37ffc7 100644 --- a/src/test/regress/expected/int4.out +++ b/src/test/regress/expected/int4.out @@ -403,3 +403,23 @@ FROM (VALUES (-2.5::numeric), 2.5 | 3 (7 rows) +-- gcd +SELECT gcd(a, b), gcd(a, -b), gcd(-a, b), gcd(-a, -b), gcd(b, a) +FROM (VALUES (61866666::int4, 6410818::int4)) AS v(a, b); + gcd | gcd | gcd | gcd | gcd +------+------+------+------+------ + 1466 | 1466 | 1466 | 1466 | 1466 +(1 row) + +SELECT gcd((-2147483648)::int4, 1073741824::int4); + gcd +------------ + 1073741824 +(1 row) + +SELECT gcd((-2147483648)::int4, (-2147483648)::int4); + gcd +------------- + -2147483648 +(1 row) + diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out index 8447a28c3d..d6d1a76b29 100644 --- a/src/test/regress/expected/int8.out +++ b/src/test/regress/expected/int8.out @@ -886,3 +886,23 @@ FROM (VALUES (-2.5::numeric), 2.5 | 3 (7 rows) +-- gcd +SELECT gcd(a, b), gcd(a, -b), gcd(-a, b), gcd(-a, -b), gcd(b, a) +FROM (VALUES (288484263558::int8, 29893644334::int8)) AS v(a, b); + gcd | gcd | gcd | gcd | gcd +---------+---------+---------+---------+--------- + 6835958 | 6835958 | 6835958 | 6835958 | 6835958 +(1 row) + +SELECT gcd((-9223372036854775808)::int8, 4611686018427387904::int8); + gcd +--------------------- + 4611686018427387904 +(1 row) + +SELECT gcd((-9223372036854775808)::int8, (-9223372036854775808)::int8); + gcd +---------------------- + -9223372036854775808 +(1 row) + diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql index 7dbafb6dac..656b200e5e 100644 --- a/src/test/regress/sql/int2.sql +++ b/src/test/regress/sql/int2.sql @@ -112,3 +112,10 @@ FROM (VALUES (-2.5::numeric), (0.5::numeric), (1.5::numeric), (2.5::numeric)) t(x); + +-- gcd +SELECT gcd(a, b), gcd(a, -b), gcd(-a, b), gcd(-a, -b), gcd(b, a) +FROM (VALUES (24948::int2, 4914::int2)) AS v(a, b); + +SELECT gcd((-32768)::int2, 16384::int2); +SELECT gcd((-32768)::int2, (-32768)::int2); diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql index f014cb2d32..1faea33945 100644 --- a/src/test/regress/sql/int4.sql +++ b/src/test/regress/sql/int4.sql @@ -155,3 +155,10 @@ FROM (VALUES (-2.5::numeric), (0.5::numeric), (1.5::numeric), (2.5::numeric)) t(x); + +-- gcd +SELECT gcd(a, b), gcd(a, -b), gcd(-a, b), gcd(-a, -b), gcd(b, a) +FROM (VALUES (61866666::int4, 6410818::int4)) AS v(a, b); + +SELECT gcd((-2147483648)::int4, 1073741824::int4); +SELECT gcd((-2147483648)::int4, (-2147483648)::int4); diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql index e890452236..af9501c415 100644 --- a/src/test/regress/sql/int8.sql +++ b/src/test/regress/sql/int8.sql @@ -225,3 +225,10 @@ FROM (VALUES (-2.5::numeric), (0.5::numeric), (1.5::numeric), (2.5::numeric)) t(x); + +-- gcd +SELECT gcd(a, b), gcd(a, -b), gcd(-a, b), gcd(-a, -b), gcd(b, a) +FROM (VALUES (288484263558::int8, 29893644334::int8)) AS v(a, b); + +SELECT gcd((-9223372036854775808)::int8, 4611686018427387904::int8); +SELECT gcd((-9223372036854775808)::int8, (-9223372036854775808)::int8); --------------A07F80366BA3A32A5E612C67--