/* arithmetic.c -- (C) Geoffrey Reynolds, April 2006.

   64 bit arithmetic functions: mod, addmod, mulmod, invmod, powmod
   (see arithmetic.h for inline functions).

   This program 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 of the License, or
   (at your option) any later version.
 */

#include <assert.h>
#include "arithmetic.h"

#ifdef GENERIC_mulmod64
double one_over_p;
double b_over_p;
#endif

#ifndef HAVE_powmod64
#define HAVE_powmod64
/*
  return b^n (mod p)
 */
uint64_t powmod64(uint64_t b, uint_fast32_t n, uint64_t p)
{
  uint64_t a;

  for (a = (n % 2) ? b : 1, n /= 2; n > 0; n /= 2)
  {
    b = mulmod64(b,b,p);
    if (n % 2)
      a = mulmod64(a,b,p);
  }

  return a;
}
#endif

#ifndef HAVE_invmod64
#define HAVE_invmod64
/*
  1/a (mod p) if sign is negative or -1/a (mod p) otherwise.
  Assumes 1 < a < p, p prime.
*/
uint64_t invmod32_64(uint32_t a, int32_t sign, uint64_t p)
{
  /* This function is a modification of mp_modinv_1() from Jason
     Papadopoulos's Msieve version 1.10. */

  /* thanks to the folks at www.mersenneforum.org */

  uint64_t ps1, ps2, q;
  uint32_t r, t, dividend, divisor;

  assert(1 < a);
  assert(a < p);
  assert(sign != 0);

  q = p / a;
  r = p % a;

  assert(r > 0);

  dividend = a;
  divisor = r;
  ps1 = q;
  ps2 = 1;
	
  while (divisor > 1)
  {
    r = dividend - divisor;
    t = r - divisor;
    if (r >= divisor) {
      q += ps1; r = t; t -= divisor;
      if (r >= divisor) {
        q += ps1; r = t; t -= divisor;
        if (r >= divisor) {
          q += ps1; r = t; t -= divisor;
          if (r >= divisor) {
            q += ps1; r = t; t -= divisor;
            if (r >= divisor) {
              q += ps1; r = t; t -= divisor;
              if (r >= divisor) {
                q += ps1; r = t; t -= divisor;
		if (r >= divisor) {
                  q += ps1; r = t; t -= divisor;
                  if (r >= divisor) {
                    q += ps1; r = t;
                    if (r >= divisor) {
                      q = dividend / divisor;
                      r = dividend % divisor;
                      q *= ps1;
                    } } } } } } } } }
    q += ps2;
    sign = -sign;
    dividend = divisor;
    divisor = r;
    ps2 = ps1;
    ps1 = q;
  }

  assert(0 < ps1);
  assert(ps1 < p);

  return (sign > 0) ? ps1 : p - ps1;
}
#endif


#if (USE_ASM && __i386__ && __GNUC__ == 3 && __GNUC_MINOR__ == 4)
/* GCC 3.4 has problems when these are inline. GCC 3.3, 4.0, 4.1 are OK. */
void mod64_init(uint64_t p)
{
  asm volatile ("fildll %1\n\t"
                "fld1\n\t"
                "fdivp %%st(0),%%st(1)"
                : "=m" (mod64_dummy) /* dummy output */
                : "m" (p) );
}

void mod64_fini(void)
{
  asm volatile ("fstp %%st(0)"
                : "=m" (mod64_dummy) /* dummy output */
                : /* no inputs */ );
}
#endif

#if (USE_ASM && __i386__ && __GNUC__ == 3 && __GNUC_MINOR__ == 4)
/* GCC 3.4 has problems when these are inline. GCC 3.3, 4.0, 4.1 are OK. */
void pre2_mulmod64_init(uint64_t b)
{
  asm volatile ("fildll %1\n\t"
                "fmul %%st(1),%%st(0)"
                : "=m" (mod64_dummy) /* dummy output */
                : "m" (b) );
}

void pre2_mulmod64_fini(void)
{
  asm volatile ("fstp %%st(0)"
                : "=m" (mod64_dummy) /* dummy output */
                : /* no inputs */ );
}
#endif


/* Setup for Mark Rodenkirch's PPC mulmod/expmod functions. See
   mulmod-ppc64.s and expmod-ppc64.s
*/
#if USE_ASM && defined(__ppc64__)
uint64_t  pMagic;
uint64_t  pShift;

void getMagic(uint64_t d)
{
   uint64_t two63 = 0x8000000000000000;

   uint64_t t = two63;
   uint64_t anc = t - 1 - t%d;    /* Absolute value of nc. */
   uint64_t p = 63;            /* Init p. */
   uint64_t q1 = two63/anc;       /* Init q1 = 2**p/|nc|. */
   uint64_t r1 = two63 - q1*anc;  /* Init r1 = rem(2**p, |nc|). */
   uint64_t q2 = two63/d;        /* Init q2 = 2**p/|d|. */
   uint64_t r2 = two63- q2*d;      /* Init r2 = rem(2**p, |d|). */
   uint64_t delta, mag;

   do {
      p = p + 1;
      q1 = 2*q1;                  /* Update q1 = 2**p/|nc|. */
      r1 = 2*r1;                  /* Update r1 = rem(2**p, |nc|. */
      if (r1 >= anc) {      /* (Must be an unsigned */
         q1 = q1 + 1;        /* comparison here). */
         r1 = r1 - anc;
      }
      q2 = 2*q2;                  /* Update q2 = 2**p/|d|. */
      r2 = 2*r2;                  /* Update r2 = rem(2**p, |d|. */
      if (r2 >= d) {          /* (Must be an unsigned */
         q2 = q2 + 1;        /* comparison here). */
         r2 = r2 - d;
      }
      delta = d - r2;
   } while (q1 < delta || (q1 == delta && r1 == 0));

   mag = q2 + 1;

   pShift = p - 64;             /* shift amount to return. */

   pMagic = mag;
}
#endif
