[project @ 1998-06-05 14:38:01 by simonm]
[ghc-hetmet.git] / ghc / rts / gmp / mpz / powm.c
diff --git a/ghc/rts/gmp/mpz/powm.c b/ghc/rts/gmp/mpz/powm.c
new file mode 100644 (file)
index 0000000..5dcd1b1
--- /dev/null
@@ -0,0 +1,276 @@
+/* mpz_powm(res,base,exp,mod) -- Set RES to (base**exp) mod MOD.
+
+Copyright (C) 1991, 1993, 1994, 1996 Free Software Foundation, Inc.
+
+This file is part of the GNU MP Library.
+
+The GNU MP Library is free software; you can redistribute it and/or modify
+it under the terms of the GNU Library General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+The GNU MP Library is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA. */
+
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+#ifndef BERKELEY_MP
+void
+#if __STDC__
+mpz_powm (mpz_ptr res, mpz_srcptr base, mpz_srcptr exp, mpz_srcptr mod)
+#else
+mpz_powm (res, base, exp, mod)
+     mpz_ptr res;
+     mpz_srcptr base;
+     mpz_srcptr exp;
+     mpz_srcptr mod;
+#endif
+#else /* BERKELEY_MP */
+void
+#if __STDC__
+pow (mpz_srcptr base, mpz_srcptr exp, mpz_srcptr mod, mpz_ptr res)
+#else
+pow (base, exp, mod, res)
+     mpz_srcptr base;
+     mpz_srcptr exp;
+     mpz_srcptr mod;
+     mpz_ptr res;
+#endif
+#endif /* BERKELEY_MP */
+{
+  mp_ptr rp, ep, mp, bp;
+  mp_size_t esize, msize, bsize, rsize;
+  mp_size_t size;
+  int mod_shift_cnt;
+  int negative_result;
+  mp_limb_t *free_me = NULL;
+  size_t free_me_size;
+  TMP_DECL (marker);
+
+  esize = ABS (exp->_mp_size);
+  msize = ABS (mod->_mp_size);
+  size = 2 * msize;
+
+  rp = res->_mp_d;
+  ep = exp->_mp_d;
+
+  if (msize == 0)
+    msize = 1 / msize;         /* provoke a signal */
+
+  if (esize == 0)
+    {
+      /* Exponent is zero, result is 1 mod MOD, i.e., 1 or 0
+        depending on if MOD equals 1.  */
+      rp[0] = 1;
+      res->_mp_size = (msize == 1 && (mod->_mp_d)[0] == 1) ? 0 : 1;
+      return;
+    }
+
+  TMP_MARK (marker);
+
+  /* Normalize MOD (i.e. make its most significant bit set) as required by
+     mpn_divmod.  This will make the intermediate values in the calculation
+     slightly larger, but the correct result is obtained after a final
+     reduction using the original MOD value.  */
+
+  mp = (mp_ptr) TMP_ALLOC (msize * BYTES_PER_MP_LIMB);
+  count_leading_zeros (mod_shift_cnt, mod->_mp_d[msize - 1]);
+  if (mod_shift_cnt != 0)
+    mpn_lshift (mp, mod->_mp_d, msize, mod_shift_cnt);
+  else
+    MPN_COPY (mp, mod->_mp_d, msize);
+
+  bsize = ABS (base->_mp_size);
+  if (bsize > msize)
+    {
+      /* The base is larger than the module.  Reduce it.  */
+
+      /* Allocate (BSIZE + 1) with space for remainder and quotient.
+        (The quotient is (bsize - msize + 1) limbs.)  */
+      bp = (mp_ptr) TMP_ALLOC ((bsize + 1) * BYTES_PER_MP_LIMB);
+      MPN_COPY (bp, base->_mp_d, bsize);
+      /* We don't care about the quotient, store it above the remainder,
+        at BP + MSIZE.  */
+      mpn_divmod (bp + msize, bp, bsize, mp, msize);
+      bsize = msize;
+      /* Canonicalize the base, since we are going to multiply with it
+        quite a few times.  */
+      MPN_NORMALIZE (bp, bsize);
+    }
+  else
+    bp = base->_mp_d;
+
+  if (bsize == 0)
+    {
+      res->_mp_size = 0;
+      TMP_FREE (marker);
+      return;
+    }
+
+  if (res->_mp_alloc < size)
+    {
+      /* We have to allocate more space for RES.  If any of the input
+        parameters are identical to RES, defer deallocation of the old
+        space.  */
+
+      if (rp == ep || rp == mp || rp == bp)
+       {
+         free_me = rp;
+         free_me_size = res->_mp_alloc;
+       }
+      else
+       (*_mp_free_func) (rp, res->_mp_alloc * BYTES_PER_MP_LIMB);
+
+      rp = (mp_ptr) (*_mp_allocate_func) (size * BYTES_PER_MP_LIMB);
+      res->_mp_alloc = size;
+      res->_mp_d = rp;
+    }
+  else
+    {
+      /* Make BASE, EXP and MOD not overlap with RES.  */
+      if (rp == bp)
+       {
+         /* RES and BASE are identical.  Allocate temp. space for BASE.  */
+         bp = (mp_ptr) TMP_ALLOC (bsize * BYTES_PER_MP_LIMB);
+         MPN_COPY (bp, rp, bsize);
+       }
+      if (rp == ep)
+       {
+         /* RES and EXP are identical.  Allocate temp. space for EXP.  */
+         ep = (mp_ptr) TMP_ALLOC (esize * BYTES_PER_MP_LIMB);
+         MPN_COPY (ep, rp, esize);
+       }
+      if (rp == mp)
+       {
+         /* RES and MOD are identical.  Allocate temporary space for MOD.  */
+         mp = (mp_ptr) TMP_ALLOC (msize * BYTES_PER_MP_LIMB);
+         MPN_COPY (mp, rp, msize);
+       }
+    }
+
+  MPN_COPY (rp, bp, bsize);
+  rsize = bsize;
+
+  {
+    mp_size_t i;
+    mp_ptr xp = (mp_ptr) TMP_ALLOC (2 * (msize + 1) * BYTES_PER_MP_LIMB);
+    int c;
+    mp_limb_t e;
+    mp_limb_t carry_limb;
+
+    negative_result = (ep[0] & 1) && base->_mp_size < 0;
+
+    i = esize - 1;
+    e = ep[i];
+    count_leading_zeros (c, e);
+    e = (e << c) << 1;         /* shift the exp bits to the left, lose msb */
+    c = BITS_PER_MP_LIMB - 1 - c;
+
+    /* Main loop.
+
+       Make the result be pointed to alternately by XP and RP.  This
+       helps us avoid block copying, which would otherwise be necessary
+       with the overlap restrictions of mpn_divmod.  With 50% probability
+       the result after this loop will be in the area originally pointed
+       by RP (==RES->_mp_d), and with 50% probability in the area originally
+       pointed to by XP.  */
+
+    for (;;)
+      {
+       while (c != 0)
+         {
+           mp_ptr tp;
+           mp_size_t xsize;
+
+           mpn_mul_n (xp, rp, rp, rsize);
+           xsize = 2 * rsize;
+           if (xsize > msize)
+             {
+               mpn_divmod (xp + msize, xp, xsize, mp, msize);
+               xsize = msize;
+             }
+
+           tp = rp; rp = xp; xp = tp;
+           rsize = xsize;
+
+           if ((mp_limb_signed_t) e < 0)
+             {
+               mpn_mul (xp, rp, rsize, bp, bsize);
+               xsize = rsize + bsize;
+               if (xsize > msize)
+                 {
+                   mpn_divmod (xp + msize, xp, xsize, mp, msize);
+                   xsize = msize;
+                 }
+
+               tp = rp; rp = xp; xp = tp;
+               rsize = xsize;
+             }
+           e <<= 1;
+           c--;
+         }
+
+       i--;
+       if (i < 0)
+         break;
+       e = ep[i];
+       c = BITS_PER_MP_LIMB;
+      }
+
+    /* We shifted MOD, the modulo reduction argument, left MOD_SHIFT_CNT
+       steps.  Adjust the result by reducing it with the original MOD.
+
+       Also make sure the result is put in RES->_mp_d (where it already
+       might be, see above).  */
+
+    if (mod_shift_cnt != 0)
+      {
+       carry_limb = mpn_lshift (res->_mp_d, rp, rsize, mod_shift_cnt);
+       rp = res->_mp_d;
+       if (carry_limb != 0)
+         {
+           rp[rsize] = carry_limb;
+           rsize++;
+         }
+      }
+    else
+      {
+       MPN_COPY (res->_mp_d, rp, rsize);
+       rp = res->_mp_d;
+      }
+
+    if (rsize >= msize)
+      {
+       mpn_divmod (rp + msize, rp, rsize, mp, msize);
+       rsize = msize;
+      }
+
+    /* Remove any leading zero words from the result.  */
+    if (mod_shift_cnt != 0)
+      mpn_rshift (rp, rp, rsize, mod_shift_cnt);
+    MPN_NORMALIZE (rp, rsize);
+  }
+
+  if (negative_result && rsize != 0)
+    {
+      if (mod_shift_cnt != 0)
+       mpn_rshift (mp, mp, msize, mod_shift_cnt);
+      mpn_sub (rp, mp, msize, rp, rsize);
+      rsize = msize;
+      MPN_NORMALIZE (rp, rsize);
+    }
+  res->_mp_size = rsize;
+
+  if (free_me != NULL)
+    (*_mp_free_func) (free_me, free_me_size * BYTES_PER_MP_LIMB);
+  TMP_FREE (marker);
+}