[project @ 1996-01-08 20:28:12 by partain]
[ghc-hetmet.git] / ghc / runtime / gmp / mpz_powm.c
diff --git a/ghc/runtime/gmp/mpz_powm.c b/ghc/runtime/gmp/mpz_powm.c
new file mode 100644 (file)
index 0000000..75949ec
--- /dev/null
@@ -0,0 +1,251 @@
+/* mpz_powm(res,base,exp,mod) -- Set RES to (base**exp) mod MOD.
+
+Copyright (C) 1991, 1992 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 General Public License as published by
+the Free Software Foundation; either version 2, 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with the GNU MP Library; see the file COPYING.  If not, write to
+the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#include "gmp.h"
+#include "gmp-impl.h"
+#include "longlong.h"
+
+#ifndef BERKELEY_MP
+void
+#ifdef __STDC__
+mpz_powm (MP_INT *res, const MP_INT *base, const MP_INT *exp,
+         const MP_INT *mod)
+#else
+mpz_powm (res, base, exp, mod)
+     MP_INT *res;
+     const MP_INT *base;
+     const MP_INT *exp;
+     const MP_INT *mod;
+#endif
+#else /* BERKELEY_MP */
+void
+#ifdef __STDC__
+pow (const MP_INT *base, const MP_INT *exp, const MP_INT *mod, MP_INT *res)
+#else
+pow (base, exp, mod, res)
+     const MP_INT *base;
+     const MP_INT *exp;
+     const MP_INT *mod;
+     MP_INT *res;
+#endif
+#endif /* BERKELEY_MP */
+{
+  mp_ptr rp, ep, mp, bp;
+  mp_size esize, msize, bsize, rsize;
+  mp_size size;
+  int mod_shift_cnt;
+  int negative_result;
+  mp_limb *free_me = NULL;
+  size_t free_me_size;
+
+  esize = ABS (exp->size);
+  msize = ABS (mod->size);
+  size = 2 * msize;
+
+  rp = res->d;
+  ep = exp->d;
+
+  /* Normalize MOD (i.e. make its most significant bit set) as required by
+     mpn_div.  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) alloca (msize * BYTES_PER_MP_LIMB);
+  count_leading_zeros (mod_shift_cnt, mod->d[msize - 1]);
+  if (mod_shift_cnt != 0)
+    (void) mpn_lshift (mp, mod->d, msize, mod_shift_cnt);
+  else
+    MPN_COPY (mp, mod->d, msize);
+
+  bsize = ABS (base->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) alloca ((bsize + 1) * BYTES_PER_MP_LIMB);
+      MPN_COPY (bp, base->d, bsize);
+      /* We don't care about the quotient, store it above the remainder,
+        at BP + MSIZE.  */
+      mpn_div (bp + msize, bp, bsize, mp, msize);
+      bsize = msize;
+      while (bsize > 0 && bp[bsize - 1] == 0)
+       bsize--;
+    }
+  else
+    {
+      bp = base->d;
+      bsize = ABS (base->size);
+    }
+
+  if (res->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->alloc;
+       }
+      else
+       (*_mp_free_func) (rp, res->alloc * BYTES_PER_MP_LIMB);
+
+      rp = (mp_ptr) (*_mp_allocate_func) (size * BYTES_PER_MP_LIMB);
+      res->alloc = size;
+      res->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) alloca (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) alloca (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) alloca (msize * BYTES_PER_MP_LIMB);
+         MPN_COPY (mp, rp, msize);
+       }
+    }
+
+  if (esize == 0)
+    {
+      rp[0] = 1;
+      res->size = 1;
+      return;
+    }
+
+  MPN_COPY (rp, bp, bsize);
+  rsize = bsize;
+
+  {
+    mp_size i;
+    mp_size xsize;
+    mp_ptr dummyp = (mp_ptr) alloca ((msize + 1) * BYTES_PER_MP_LIMB);
+    mp_ptr xp = (mp_ptr) alloca (2 * (msize + 1) * BYTES_PER_MP_LIMB);
+    int c;
+    mp_limb e;
+    mp_limb carry_limb;
+
+    negative_result = (ep[0] & 1) && base->size < 0;
+
+    i = esize - 1;
+    e = ep[i];
+    count_leading_zeros (c, e);
+    e <<= (c + 1);             /* shift the exp bits to the left, loose msb */
+    c = BITS_PER_MP_LIMB - 1 - c;
+
+    /* Main loop.
+
+       Make the result be pointed to alternatingly by XP and RP.  This
+       helps us avoid block copying, which would otherwise be necessary
+       with the overlap restrictions of mpn_div.  With 50% probability
+       the result after this loop will be in the area originally pointed
+       by RP (==RES->D), and with 50% probability in the area originally
+       pointed to by XP.  */
+
+    for (;;)
+      {
+       while (c != 0)
+         {
+           mp_ptr tp;
+           mp_size tsize;
+
+           xsize = mpn_mul (xp, rp, rsize, rp, rsize);
+           mpn_div (dummyp, xp, xsize, mp, msize);
+
+           /* Remove any leading zero words from the result.  */
+           if (xsize > msize)
+             xsize = msize;
+           while (xsize > 0 && xp[xsize - 1] == 0)
+             xsize--;
+
+           tp = rp; rp = xp; xp = tp;
+           tsize = rsize; rsize = xsize; xsize = tsize;
+
+           if ((mp_limb_signed) e < 0)
+             {
+               if (rsize > bsize)
+                 xsize = mpn_mul (xp, rp, rsize, bp, bsize);
+               else
+                 xsize = mpn_mul (xp, bp, bsize, rp, rsize);
+               mpn_div (dummyp, xp, xsize, mp, msize);
+
+               /* Remove any leading zero words from the result.  */
+               if (xsize > msize)
+                 xsize = msize;
+               while (xsize > 0 && xp[xsize - 1] == 0)
+                 xsize--;
+
+               tp = rp; rp = xp; xp = tp;
+               tsize = rsize; rsize = xsize; xsize = tsize;
+             }
+           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->D (where it already
+       might be, see above).  */
+
+    carry_limb = mpn_lshift (res->d, rp, rsize, mod_shift_cnt);
+    rp = res->d;
+    if (carry_limb != 0)
+      {
+       rp[rsize] = carry_limb;
+       rsize++;
+      }
+    mpn_div (dummyp, rp, rsize, mp, msize);
+    /* Remove any leading zero words from the result.  */
+    if (rsize > msize)
+      rsize = msize;
+    while (rsize > 0 && rp[rsize - 1] == 0)
+      rsize--;
+    rsize = mpn_rshift (rp, rp, rsize, mod_shift_cnt);
+  }
+
+  res->size = negative_result >= 0 ?  rsize : -rsize;
+
+  if (free_me != NULL)
+    (*_mp_free_func) (free_me, free_me_size * BYTES_PER_MP_LIMB);
+
+  alloca (0);
+}