14

I am working on a mathematical problem that involves coprime integers. I wrote a computer program that allows me to search for the numbers I am looking for. However I am looking at a large set of integers and I have to compare many pairs of numbers and determine if they are coprime. My program has to do this so many times that any reduction in the calculation time for each pair of numbers would significantly reduce the overall run time of the program.

I am currently using the Euclidean algorithm.

http://en.wikipedia.org/wiki/Euclidean_algorithm

Even though the Euclidean algorithm is a very efficient method, I don't know if it's the fastest. I don't need the gcd of the two numbers I just need to determine if they are coprime or not.

My pseudocode:

while (B ≠ 0)        
 {T = B    
 B = A mod T    
 A = T}

where A and B are the pair of integers in question and T is a dummy variable used to hold a value to be used in a later calculation. For those who haven't written a computer program before, while means that the process in the brackets will repeat until the condition in the parentheses is false. At the end of the loop the variable A becomes the gcd of the original two integers. if A=1 the two numbers are coprime if A>1 then the numbers are not coprime.

Even though the program above is simple, it is an iterative process and I'm looking for a method that only needs one or two steps.

Thanks in advance!

qwr
  • 11,362
quantus14
  • 2,614
  • 3
    Depending on machine architecture and a few other things, it might be faster to use the binary GCD algorithm (with lookup table). Worth a try if gcd is a bottleneck. – Daniel Fischer Jul 16 '13 at 22:05
  • 2
    Barring some miraculous discovery in factorization, I think the Euclidean Algorithm is the fastest way to determine if two positive integers are coprime. It is linear in the number of digits; not many algorithms get faster than that. – robjohn Jul 16 '13 at 22:06
  • 2
    How big is the set of integers you're looking at? Are we talking about tens, thousands or millions of elements? And do you need to compare all pairs of them or just some? Maybe describing the actual problem could help too; sometimes it's possible to reduce the number of coprimality checks considerably, so that one doesn't need to spend too much time optimizing them in the first place. – Peter Košinár Jul 16 '13 at 22:10
  • 1
    The set is roughly a million elements and the integers that don't need to be checked have already been filtered out. – quantus14 Jul 16 '13 at 22:26
  • I don't think you can do much better than the Euclidean algorithm. Tweaks to reduce the number of steps seem likely to increase the time per step and thus be counterproductive. One possible exception is that, if at any stage of the algorithm you have an even number and an odd one, you might as well divide the even one by two. If your programming language takes advantage of the bit representation of integers, checking parity and dividing by two could be very fast. – Andreas Blass Jul 16 '13 at 22:34
  • @robjohn If one is genuinely interested in the asymptotic limit of large $n$ where $A,B$ are $n$-bit numbers then in fact the algorithm given is not linear in $n$. In fact, it is (at least) quadratic in $n$. – not all wrong Jul 16 '13 at 22:48
  • Knowing where the numbers come from could be helpful. Aside from that, be sure to write your program to take advantage of your hardware—vector processing capabilities, multiple cores/CPUs, etc. can often give you substantial constant-factor reductions with some work. Also, watch out for pipeline and (even more so) cache effects. – dfeuer Jul 16 '13 at 23:36
  • @Sharkos: are you counting the number of digit-from-digit subtractions? I thought that the number of full subtractions is $O(\log(n))$. – robjohn Jul 17 '13 at 00:07
  • @robjohn: Sticking to $n$ bit numbers, I thought there are $n$ steps of the above loop, with each containing arithmetic manipulations of $n$ bit numbers which each take at least $\Omega(n)$ time. (I imagine modular division is longer than this, but we get a quadratic lower bound from assuming all bits are processed somehow.) – not all wrong Jul 17 '13 at 00:12
  • The binary GCD algorithm allows one further optimization (early abort): once you find that both numbers are even, you can conclude that the numbers are not coprime without having to carry out the rest of the gcd computation. – ccorn Jul 25 '13 at 12:40
  • @quantus14 Use a predefined library. Especially if it is GMP (Gnu Multiple Precision Arithmetic Library). They have GCD functions. You won't be able to write anything faster. – DanielV Jan 17 '15 at 16:11

3 Answers3

7

Needed a fast test, the fastest one I came up so far in C++ (faster than using binary GCD and @ManAndPC solution):

template<class T>
inline T gcd(T a, T b) {
    while(b) {
        auto t = a % b;
        a = b;
        b = t;
    }
    return a;
}

template<class T>
inline bool are_coprimes(T a, T b) {
    if(!((a | b) & 1))
        return false; // Both are even numbers, divisible by at least 2.
    return 1 == gcd(a, b);
}

Update: much faster gcd:

inline unsigned gcd(unsigned u, unsigned v)
{
    auto shift = __builtin_ctz(u | v);
    u >>= __builtin_ctz(u);
    do {
        v >>= __builtin_ctz(v);
        if(u > v)
            std::swap(u, v);
    } while((v -= u));
    return u << shift;
}

From http://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor/

  • 1
    There is a typo in the name of the are_coprimes function. – user11171 Mar 18 '18 at 15:14
  • 1
    @CinCout It will always be false if either a or b are odd. You can take the expression and use commutative rules to turn it into the following logically equivalent statement: !((a & 1) | (b & 1)).

    Consider a number n. If n is odd, (n & 1) will always be 1. If n is even, (n & 1) will always be 0.

    Only if a and b are even will the expression ((a & 1) | (b & 1)) be 0, so when the expression is negated using ! it will be truthy. If either a or b are odd, ((a & 1) | (b & 1)) will be 1, negated will be falsy.

    – kevin.groat May 17 '18 at 17:32
  • 1
    The second gcd function only works if it's compiled using GCC, as __builtin_ctz() is a GCC intrinsic function (ctz stands for "Calculate Trailing Zeroes"). For unsigned longs, you can swap it out for __builtin_ctzl() or for unsigned long longs, __builtin_ctzll(). Source: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html – kevin.groat May 17 '18 at 17:41
4

Since you care about the speed of IsCoprime(n, m), I'm assuming that you're calling that function many times. So let's assume that we're minimizing the time the following code takes to run (K is some constant):

for (int n = 1; n <= K; ++n)
  for (int m = 1; m <= K; ++m)
    IsCoprime(n, m);

You can precompute prime factors all ints up to K, which takes $O(K \log K)$, much smaller than the number of invocations of IsCoprime above, which is $O(K^2)$.

Then for each int up to K, compute its 32-bit "prime factor signature" sig[n].

  • Bit 0 is set if n is divisible by 2
  • Bit 1 is set if n is divisible by 3
  • Bit 2 is set if n is divisible by 5
  • ...
  • Bit 30 is set if n is divisible by 113
  • Bit 31 is set if n is divisible by some prime > 113

Now, the first step you do in IsCoprime(n, m) is to compute binary and of the signatures of its arguments: X = sig[n] & sig[m]. If X is 0, then n and m are coprime. Otherwise, if some bit other than bit 31 of X is set, then n and m are not coprime. If neither of these conditions hold, you need to run some GCD algorithm to get the answer. However, you'll need to call GCD in a small number of cases.

One improvement you can make is having each of bits 16..31 represent multiple prime factors. For example, bit 16 would be set if n is divisible by any of the primes between 47 and 97, or something like that.

And of course you can always use more bits, like 64.

When I tried these techniques for K roughly 30,000, I got an over 10x speed improvement over (binary) GCD.

EDIT (2022-07-11)

Here's a concrete implementation of this idea: https://gist.github.com/zielaj/cfdeca92fc0b8371164e4ca521d83587

For K = 0x4000 (16K), it runs in 0.337s on my laptop, vs 5.798s for binary GCD.

This code uses 64-bit signatures. The lower 32 bits are dedicated to the first 32 primes. Subsequent primes get 2 "random" upper bits each, an approach inspired by Bloom filters: https://en.wikipedia.org/wiki/Bloom_filter

Here's the code snippet for the actual coprimality testing:

  inline bool AreCoprime(uxint n, uxint m) {
    if (n == 0 || m == 0) return n + m == 1;
    uxint sig = signatures[n] & signatures[m];
// If the signatures are disjoint, then there are no prime factors
// in common, so the numbers are coprime.
if (sig == 0) return true;

// If the signatures overlap somewhere in the first 32 bits, then
// there's a common prime factor because each of these bits is
// dedicated to a single prime.
if ((sig &amp; uniquebits) != 0) return false;

// If there's at most one bit overlap (in the second half of the
// signature), then there are no prime factors in common because
// each of the primes in the second half gets 2 bits.
if ((sig &amp; (sig - 1)) == 0) return true;

// If we're still undecided, fall back to the GCD computation.
return bgcd(n, m) == 1;

}

Note that if you just want to generate pairs of small coprime integers, you can also use Stern-Brocot tree, which is O(1) per pair. However, when I tried that, using an admittedly unoptimized straightforward recursive implementation, it was still slower than the code above. https://en.wikipedia.org/wiki/Stern%E2%80%93Brocot_tree

  • There's a space/speed trade-off here. How do you store and reaccess the primes of each integer you've pre-computed? – Lorenzo Jul 10 '22 at 21:06
  • 1
    All you store for each number is the 32-bit signature describe above, so the memory usage is 4*K bytes. – user1020406 Jul 11 '22 at 08:05
  • 1
    I've now included a link to a complete standalone implementation as well as a documented code snippet for the coprimality testing function. – user1020406 Jul 11 '22 at 08:49
3

Here a version in C sharp (the fastest for me)

static bool coprime(long u, long v)
{
    if (((u | v) & 1) == 0) return false;

    while ((u & 1) == 0) u >>= 1;
    if (u == 1) return true;

    do
    {
        while ((v & 1) == 0) v >>= 1;
        if (v == 1) return true;

        if (u > v) { long t = v; v = u; u = t; }
        v -= u;
    } while (v != 0);

    return false;
}