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

   arithmetic functions: mulmod, invmod, powmod.

   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.
 */

#ifndef _ARITHMETIC_H
#define _ARITHMETIC_H

#include <stdint.h>
#include "config.h"

#if USE_ASM
# define NEED_mulmod64
# define NEED_powmod64
# define NEED_invmod64
# if USE_MOD_PRODUCTS
#  define NEED_rem64_32
# endif
# if (__i386__ && __GNUC__)
#  include "asm-i386-gcc.h"
# elif defined(__ppc64__)
#  include "asm-ppc64.h"
# elif (__x86_64__ && __GNUC__)
#  include "asm-x86-64-gcc.h"
# endif
#endif

/*
  Use generic code for any functions not defined above in assembler.
*/


/* arithmetic64.c */

#ifndef HAVE_mulmod64
#define HAVE_mulmod64
#define GENERIC_mulmod64
extern double one_over_p;
/*
  a * b (mod p)
*/

/* This is based on code by Phil Carmody posted in sci.crypt. It should
   work for a,b,p up to 52 bits on non-x86 architectures.
*/
#define MOD64_GREATEST_PRIME UINT64_C(4503599627370449)
#define MOD64_SIEVE_LIMIT "2^52"

static inline uint64_t mulmod64(uint64_t a, uint64_t b, uint64_t p)
{
  int64_t tmp, ret;

  tmp = p * (int64_t)((double)a*b*one_over_p);  /* p*(ab/p) */
  ret = a*b - tmp;

  if (ret < 0)
    ret += p;
  else if (ret >= p)
    ret -= p;

  return ret;
}

static inline void mod64_init(uint64_t p)
{
  one_over_p = 1.0/p;
}

static inline void mod64_fini(void)
{
}

extern double b_over_p;

static inline uint64_t pre2_mulmod64(uint64_t a, uint64_t b, uint64_t p)
{
  int64_t tmp, ret;

  tmp = p * (int64_t)((double)a*b_over_p);  /* p*(ab/p) */
  ret = a*b - tmp;

  if (ret < 0)
    ret += p;
  else if (ret >= p)
    ret -= p;

  return ret;
}

static inline void pre2_mulmod64_init(uint64_t b)
{
  b_over_p = (double)b * one_over_p;
}

static inline void pre2_mulmod64_fini(void)
{
}
#endif


#ifndef HAVE_powmod64
/*
  b^n (mod p)
*/
uint64_t powmod64(uint64_t b, uint_fast32_t n, uint64_t p) attribute ((pure));
#endif


#ifndef 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) attribute ((const));
#endif

#endif /* _ARITHMETIC_H */
