//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : bigint_appl.c (based on the GNU Integer test) 
// Author      : Thomas Papanikolaou (TP)
// Last change : TP, Feb  7 1995, initial version
//		 MM, Sep 17 1996
//		    added test c=a;a.divide_by_2() == c/2;
//		    added misctest() for special cases of the
//	 	    power functions
//		 MM, Sep 25 1996
//		    added operator_test() for postfix-operators
//

#include <LiDIA/bigint.h>
#include <assert.h>

bigint factorial(bigint n)
{
  bigint f;
  if (n < 0)
    f = 0;
  else
  {
    f = 1;
    while (n > 0)
    {
      f *= n;
      --n;
    }
  }
  return f;
}


bigint fibonacci(long n)
{
  bigint f;
  if (n <= 0)
    f = 0;
  else
  {
    f = 1;
    bigint prev = 0;
    bigint tmp;
    while (n > 1)
    {
      tmp = f;
      f += prev;
      prev = tmp;
      --n;
    }
  }
  return f;
}

void identitytest(bigint& a, bigint& b, bigint& c)
{
  assert( -(-a) ==  a);
  assert( (a + b) ==  (b + a));
  assert( (a + (-b)) ==  (a - b));
  assert( (a * b) ==  (b * a));
  assert( (a * (-b)) ==  -(a * b));
  assert( (a / (-b)) ==  -(a / b));
  assert( (a - b) ==  -(b - a));
  assert( (a + (b + c)) ==  ((a + b) + c));
  assert( (a * (b * c)) ==  ((a * b) * c));
  assert( (a * (b + c)) ==  ((a * b) + (a * c)));
  assert( ((a - b) + b) ==  a);
  assert( ((a + b) - b) ==  a);
  assert( ((a * b) / b) ==  a);
  assert( ((a * b) % b) ==  0);
  assert( (b * (a / b) + (a % b)) ==  a);
  assert( ((a + b) % c) ==  ((a % c) + (b % c)) % c);
}


void utiltest(bigint& a)
{
  bigint b,c;
  square(b, a);
  sqrt(b, b);
  assert(b == a);

  sqrt(b, a);
  square(b, b);
  assert(b <= a);

  bigint x = 1;
  for (int i = 0; i < 10; ++i)
  {
    power(b, a, i);
    assert(b == x);
    x *= a;
  }
  x.assign_one();
  assert(x.is_one());
  assert(x.is_odd());
  assert(!x.is_even());
  x.assign(4);
  assert(x.is_even());
  assert(!x.is_odd());
  assert(x % 4 == 0);

   b=a;
   b.divide_by_2(); assert(b == (a/(bigint)2));

   b=a; b.negate();
   b.divide_by_2(); assert(b == ((-a)/(bigint)2));
}

void bittest(bigint& a, bigint& b, bigint& c)
{
  assert( (a | b) ==  (b | a));
  assert( (a & b) ==  (b & a));
  assert( (a ^ b) ==  (b ^ a));
  assert( (a | (b | c)) ==  ((a | b) | c));
  assert( (a & (b & c)) ==  ((a & b) & c));
  assert( (a & (b | c)) ==  ((a & b) | (a & c)));
  assert( (a | (b & c)) ==  ((a | b) & (a | c)));
  assert( (a & (a | b)) ==  a);
  assert( (a | (a & b)) ==  a);
}

void accumtest(bigint& a, bigint& b, bigint& c)
{
  bigint x = a;
  x *= b;
  assert(x == (a * b));
  x += c;
  assert(x == ((a * b) + c));
  x -= a;
  assert(x == (((a * b) + c) - a));
  x /= b;
  assert(x == ((((a * b) + c) - a) / b));
  x %= c;
  assert(x == (((((a * b) + c) - a) / b) % c));
  x &= a;
  assert(x == ((((((a * b) + c) - a) / b) % c) & a));
  x |= b;
  assert(x == (((((((a * b) + c) - a) / b) % c) & a) | b));
  x ^= c;
  assert(x == ((((((((a * b) + c) - a) / b) % c) & a) | b) ^ c));
}

void longidentitytest(bigint& a, long b, long c)
{
  assert( (a + b) ==  (b + a));
  assert( (a + (-b)) ==  (a - b));
  assert( (a * b) ==  (b * a));
  assert( (a * (-b)) ==  -(a * b));
  assert( (a / (-b)) ==  -(a / b));
  assert( (a - b) ==  -(b - a));
  assert( (a + (b + c)) ==  ((a + b) + c));
  assert( (a * (b * c)) ==  ((a * b) * c));
  assert( (a * (b + c)) ==  ((a * b) + (a * c)));
  assert( ((a - b) + b) ==  a);
  assert( ((a + b) - b) ==  a);
  assert( ((a * b) / b) ==  a);
  assert( ((a * b) % b) ==  0);
  assert( (b * (a / b) + (a % b)) ==  a);
  assert( ((a + b) % c) ==  ((a % c) + (b % c)) % c);
}

void longbittest(bigint& a, long b, long c)
{
  assert( (a | b) ==  (b | a));
  assert( (a & b) ==  (b & a));
  assert( (a ^ b) ==  (b ^ a));
  assert( (a | (b | c)) ==  ((a | b) | c));
  assert( (a & (b & c)) ==  ((a & b) & c));
  assert( (a & (b | c)) ==  ((a & b) | (a & c)));
  assert( (a | (b & c)) ==  ((a | b) & (a | c)));
  assert( (a & (a | b)) ==  a);
  assert( (a | (a & b)) ==  a);
}

void longaccumtest(bigint& a, long b, long c)
{
  bigint x = a;
  x *= b;
  assert(x == (a * b));
  x += c;
  assert(x == ((a * b) + c));
  x -= a;
  assert(x == (((a * b) + c) - a));
  x /= b;
  assert(x == ((((a * b) + c) - a) / b));
  x %= c;
  assert(x == (((((a * b) + c) - a) / b) % c));
  x &= a;
  assert(x == ((((((a * b) + c) - a) / b) % c) & a));
  x |= b;
  assert(x == (((((((a * b) + c) - a) / b) % c) & a) | b));
  x ^= c;
  assert(x == ((((((((a * b) + c) - a) / b) % c) & a) | b) ^ c));
}

 
void anothertest()
{
  bigint pow64;
  power(pow64, 2, 64);
  cout << "power(pow64, 2, 64) = " << pow64 << "\n";
  cout << "pow64.bit_length() = " << pow64.bit_length() << "\n";
  assert(pow64.bit_length() == 65);

  bigint s64 = 1;
  s64 <<= 64;
  cout << "s64 = 1 << 64 = " << s64 << "\n";

  assert(s64 == pow64);
  assert(s64 >= pow64);
  assert(s64 <= pow64);
  assert(!(s64 != pow64));
  assert(!(s64 > pow64));
  assert(!(s64 < pow64));
  
  bigint s32 = s64 >> 32;
  cout << "s32 = s64 >> 32 = " << s32 << "\n";
  assert(s32.bit_length() == 33);
  assert(!(pow64 == s32));
  assert(!(pow64 < s32));
  assert(!(pow64 <= s32));
  assert(pow64 != s32);
  assert(pow64 >= s32);
  assert(pow64 > s32);

  bigint comps64 = ~s64;
  cout << "comps64 = ~s64 = " << comps64 << "\n";

  bigint result = (comps64 & s32);
  cout << "comps64 & s32 = " << result << "\n";
  result = (comps64 | s32);
  cout << "comps64 | s32 = " << result << "\n";
  result = (comps64 ^ s32);
  cout << "comps64 ^ s32 = " << result << "\n";

  assert( (s64 / s32)==s32);

  identitytest(s64, s32, comps64);
  bittest(s32, s64, comps64);
  accumtest(comps64, s32, pow64);
  utiltest(s32);
  longidentitytest(s64, 1000, 50);
  longbittest(s64, 12345, 67890);
  longaccumtest(s32, 100000, 1);
}

void iotest()
{
  bigint result;

  cout << "\nenter an Integer: ";
  cin >> result;
  cout << "number = " << result << "\n";
}

void fibtest()
{
  bigint fib50 = fibonacci(50);
  cout << "fib50 = fibonacci(50) = " << fib50 << "\n";
  bigint fib48 = fibonacci(48);
  cout << "fib48 = fibonacci(48) = " << fib48 << "\n";

  bigint result = fib48 + fib50;
  cout << "fib48 + fib50 = " << result << "\n";
  result = fib48 - fib50;
  cout << "fib48 - fib50 = " << result << "\n";
  result = fib48 * fib50;
  cout << "fib48 * fib50 = " << result << "\n";
  result = fib48 / fib50;
  cout << "fib48 / fib50 = " << result << "\n";
  result = fib48 % fib50;
  cout << "fib48 % fib50 = " << result << "\n";
  result = gcd(fib50, fib48);
  cout << "gcd(fib50, fib48) = " << result << "\n";
  sqrt(result, fib50);
  cout << "sqrt(fib50) = " << result << "\n";

  identitytest(result, fib50, fib48);
  bittest(result, fib50, fib48);
  accumtest(result, fib50, fib48);
  utiltest(fib48);
  longidentitytest(fib50, 1000, 50);
  longaccumtest(fib48, 100000, 1);
}

void facttest(bigint& one, bigint& two)
{
  bigint fact30(factorial(30));
  cout << "fact30 = factorial(30) = " << fact30 << "\n";

  bigint fact28(factorial(28));
  cout << "fact28 = factorial(28) = " << fact28 << "\n";
  assert(fact30 == fact28 * 870);

  bigint result = fact30 + fact28;
  cout << "fact30 + fact28 = " <<  result << "\n";
  result = fact30 - fact28;
  cout << "fact30 - fact28 = " << result << "\n";
  result = fact30 * fact28;
  cout << "fact30 * fact28 = " << result << "\n";
  result = fact30 / fact28;
  cout << "fact30 / fact28 = " << result << "\n";
  result = fact30 % fact28;
  cout << "fact30 % fact28 = " << result << "\n";

  result = -fact30;
  cout << "-fact30 = " << result << "\n";
  assert(abs(result) == fact30);

  cout << "lg(fact30) = " << fact30.bit_length() << "\n";
  assert(fact30.bit_length() == 108);

  result = gcd(fact30, fact28);
  cout << "gcd(fact30, fact28) = " << result << "\n";
  assert(result == fact28);

  sqrt(result, fact30);
  cout << "sqrt(fact30) = " << result << "\n";

  bigint negfact31 = fact30 * -31;
  bigint posfact31 = abs(negfact31);
  cout << "negfact31 = " << negfact31 << "\n";
  result = fact30 + negfact31;
  cout << "fact30 + negfact31 = " << result << "\n";
  result = fact30 - negfact31;
  cout << "fact30 - negfact31 = " << result << "\n";
  result = fact30 * negfact31;
  cout << "fact30 * negfact31 = " << result << "\n";
  result = fact30 / negfact31;
  cout << "fact30 / negfact31 = " << result << "\n";
  result = fact30 % negfact31;
  cout << "fact30 % negfact31 = " << result << "\n";
  result = gcd(fact30, negfact31);
  cout << "gcd(fact30, negfact31) = " << result << "\n";
  assert(result == fact30);

  identitytest(one, one, one);
  identitytest(one, one, one);
  identitytest(one, two, fact30);
  identitytest(fact30, posfact31, fact28);
  identitytest(fact30, negfact31, fact28);
  identitytest(negfact31, posfact31, fact28);

  bittest(one, one, one);
  bittest(one, one, one);
  bittest(one, two, fact30);
  bittest(fact30, posfact31, fact28);

  accumtest(one, one, one);
  accumtest(one, one, one);
  accumtest(one, two, fact30);
  accumtest(fact30, posfact31, fact28);

  utiltest(one);
  utiltest(fact30);
  utiltest(posfact31);

  longidentitytest(one, 1, 1);
  longidentitytest(one, 2, 3);
  longidentitytest(fact30, 3, -20);
  longidentitytest(fact30, 4, 20000);
  longidentitytest(negfact31, -100, 20000);

  longbittest(one, 1, 1);
  longbittest(one, 2, 3);
  longbittest(fact30, 4, 20000);
  longbittest(fact28, 1000, 50);

  longaccumtest(one, 1, 1);
  longaccumtest(one, 2, 3);
  longaccumtest(fact30, 4, 20000);
  longaccumtest(fact30, 1000, 50);
  longaccumtest(fact28, 10000000, 100000000);
}

void modtest()
{
  bigint b, e, m;

  m = 1; m <<= 32;
  b = m + 1;

  power(e, 2, 32);
  power(b, 2, 32); // use b as a comparison
  cout << "2^32 = " << e << "\n";

  e %= (e-1);                         // do same op two ways...
  b = b % (b - 1);

  cout << "2^32 % (2^32-1) = " << e << "\n"; // e is incorrect here
  cout << "2^32 % (2^32-1) = " << b << "\n"; // but b is ok
}


void misctest ()
{
  bigint a,c;

  // exponent is a long

  a.assign_zero(); // a = 0
  c.assign_one(); // just to be on the safe side
  power(c,a,(long)5);  assert(c.is_zero()); c.assign_one();
  power(c,a,(long)-5); assert(c.is_zero()); 
  power(c,a,(long)0);  assert(c.is_one()); 

  a.assign_one(); // a = 1
  c.assign_zero();
  power(c,a,(long)5);  assert(c.is_one()); c.assign_zero();
  power(c,a,(long)-5); assert(c.is_one()); c.assign_zero();
  power(c,a,(long)0);  assert(c.is_one()); c.assign_zero();

  a.negate(); // a = -1
  power(c,a,(long)5);  assert(c == a);      c.assign_zero();
  power(c,a,(long)-5); assert(c == a);      c.assign_zero();
  power(c,a,(long)0);  assert(c.is_one());  c.assign_zero();
  power(c,a,(long)4);  assert(c.is_one());  c.assign_zero();
  power(c,a,(long)-4); assert(c.is_one()); 

  add(a,a,a); // a = -2
  power(c,a,(long)-5); assert(c.is_zero());


  // exponent is a bigint

  a.assign_zero(); // a = 0
  c.assign_one(); // just to be on the safe side
  power(c,a,(bigint)5);  assert(c.is_zero()); c.assign_one();
  power(c,a,(bigint)-5); assert(c.is_zero()); 
  power(c,a,(bigint)0);  assert(c.is_one()); 

  a.assign_one(); // a = 1
  c.assign_zero();
  power(c,a,(bigint)5);  assert(c.is_one()); c.assign_zero();
  power(c,a,(bigint)-5); assert(c.is_one()); c.assign_zero();
  power(c,a,(bigint)0);  assert(c.is_one()); c.assign_zero();

  a.negate(); // a = -1
  power(c,a,(bigint)5);  assert(c == a);      c.assign_zero();
  power(c,a,(bigint)-5); assert(c == a);      c.assign_zero();
  power(c,a,(bigint)0);  assert(c.is_one());  c.assign_zero();
  power(c,a,(bigint)4);  assert(c.is_one());  c.assign_zero();
  power(c,a,(bigint)-4); assert(c.is_one()); 

  add(a,a,a); // a = -2
  power(c,a,(bigint)-5); assert(c.is_zero());
}

void operator_test ( const bigint & s )
{
   bigint a, b, c;

   a = s;
   b = s;
   c = ++a; 
   inc(b); 

   assert (c == b);
   assert (a == b);

   a = s;
   b = s;
   c = a++; 
   inc(b); 

   assert (c == s);
   assert (a == b);

   a = s;
   b = s;
   c = --a; 
   dec(b); 

   assert (c == b);
   assert (a == b);

   a = s;
   b = s;
   c = a--; 
   dec(b); 

   assert (c == s);
   assert (a == b);
}



int main()
{
  bigint one = 1;
  cout << "one = " << one << "\n";
  assert(one == 1);
  cout << "one + 1 = " << (one + 1) << "\n";

  bigint two = 2;
  cout << "two = " << two << "\n";
  assert(two == 2);

  bigint n(0);
  assert(n.is_zero());

  bigint a = randomize ((bigint)12345);
  bigint b = randomize ((bigint)678);
  power (a,a,b);

  facttest(one, two);
  fibtest();
  anothertest();
  iotest();
  modtest();
  misctest();
  operator_test(a);

  cout << "\nEnd of test\n";
  return 0;
}
