gcd_11

Niels Möller nisse at lysator.liu.se
Fri Aug 16 22:02:16 UTC 2019


After discussing several interesting gcd algorithms with Torbjörn, I've
made this observation that I think is kind-of interesting.

Lehmer code depends on the fact that if M is a 2x2 integer matrix with
determinant ±1, and

  (a';b') = M (a;b), 

then gcd (a,b) = gcd (a', b'). But for binary gcd, we can relax this: If
the matrix determinant is a power of two (in absolute value), then
gcd(a,b) and gcd(a',b') can differ only by a power of two. And in the
binary algorithm, we discard powers of two as we go, so they're no
problem.

In addition, many small matrices have power-of-two determinant, e.g.,
det(1,1;1,-3) = -4.

So we can choose a', b' quite freely among various linear combinations,
e.g., a+b, a-b, a+3b; we can chose any pair as long as the corresponding
determinant is a power of two. One way to arrange it that I think may be
simple and efficient is to chose constants m, k so that

  a' = a + kb = 0 (mod 8)
  b' = a + mb = 4 (mod 8)

This can be done by first choosing a linear combination that is
divisible by 4, and set t = (a + b) / 4 or t = (a + 3b) / 4 (that's
pretty standard).

But next, form a', b' as t and t - b, one of which is even and the
other odd. Corresponding matrices seem to satisfy the requirement: since
det (1,k;1,m) = m - k, we can use any distinct pair of numbers in {-3,
-1, 1, 3 } except the pair ±3.

Algorithm (which is made a bit more complicated by the implicit lsb
trick):

mp_limb_t
mpn_gcd_11 (mp_limb_t u, mp_limb_t v)
{
  ASSERT (u & v & 1);

  /* In this loop, we represent the odd numbers ulimb and vlimb
     without the redundant least significant one bit. This reduction
     in size by one bit avoids overflow. */
  u >>= 1;
  v >>= 1;
  
  for (;;) 
    {
      mp_limb_t n0, n1;
      int c;
      /* In this iteration, we set
         
           u' = u + k v = 0 (mod 8), v' = u + m v = 4 (mod 8),
         
         with k, m chosen from the set { -3, -1, 1, 3 }. It's arranged
         bitwise; first set t = (a + {1,3} b) / 4, then use t and t -
         b for the next iteration, keeping track of which one is odd so
         that we only need one count_trailing_zeros.
      */
      mp_limb_t t = u + v + 1;  /* (u + v) / 2, explicit lsb */
      mp_limb_t m0 = - (t & 1);
      t = (t >> 1) + (m0 & (v + 1)); /* ((u + v)/ 2 + m0 v) / 2 */
      mp_limb_t m1 = - (t & 1);
      u = (t >> 1) - (m1 & v); /* Make even, then shift out lsb 0 */
      if (u == 0)
        return (v << 1) + 1;
      count_trailing_zeros (c, u);
      v = (t >> 1) + (~m1 & ~v); /* Make odd, shift out lsb 1 */

      /* Take absolute values */
      n0 = LIMB_HIGHBIT_TO_MASK (u);
      n1 = LIMB_HIGHBIT_TO_MASK (v);
      u = ((u^n0) - n0) >> (c+1);
      /* Negation only happens when ~m1, no -n1 since that's canceled
         by the implicit bit. */
      v ^= n1;
    }
}

Seems to work, but not particularly fast :-( It's unclear to me what
progress actually is made in one iteration. There's also a long
dependency chain, it could probably be shortened a bit by computing the
m1 mask directly from the inputs, so it could be scheduled in parallel
with theoperations involving m0.

I also wonder if it can be extended to larger matrices; if one can find
matrices with k bit elements that lets us clear 2k bits most of the
time? Power-of-two determinant is a constraint, but there are still lots
of matrices.

The idea is a bit similar to the binary recursive gcd by Stehlé and
Zimmermann, but here we're aiming for fairly small numbers with matrix
selected by table lookup, with no need for a divide-and-conquer
algorithm.

Regards,
/Niels

-- 
Niels Möller. PGP-encrypted email is preferred. Keyid 368C6677.
Internet email is subject to wholesale government surveillance.



More information about the gmp-devel mailing list