//
// LiDIA - a library for computational number theory
//   Copyright (c) 1997 by the LiDIA Group
//
// File        : bigfloat_int_appl.c 
// Author      : Thomas Papanikolaou (TP), Patrick Theobald (PT)
// Last change : PT, Jan 23 1997, initial version
//

#include <LiDIA/bigfloat_int.h>
#include <stdlib.h>

#define LIDIA_DEBUG

#define lidia_assert(ex) if (ex == 0) \
                         { \
		           cerr << "\nbigfloat_int::lidia_assertion failed::"; \
		           cerr << "file \"" << __FILE__ << "\"::"; \
		           cerr << "line \"" << __LINE__ << "\"\n"; \
			   abort(); \
                         }
inline static void
close_enough(const bigfloat_int & a, const bigfloat_int & b)
{
  bigfloat_int r = a - b;
  bigfloat d;	
  r.approx(d);
  int x = d.is_approx_zero(); 
  lidia_assert(x);
}

void accu_test(const bigfloat_int &a,
	       const bigfloat_int &b,
	       const bigfloat_int &c)
{
  bigfloat_int x = a;
  x *= b;
  close_enough(x, (a * b));
  x += c;
  close_enough(x, ((a * b) + c));
  x -= a;
  close_enough(x, (((a * b) + c) - a));
  x /= b;
  close_enough(x, ((((a * b) + c) - a) / b));
}


void identity_test(const bigfloat_int &a,
		   const bigfloat_int &b,
		   const bigfloat_int &c)
{

  // (|R, +, *) is a field
  // (|R, +), (|R, *) are abelian groups and moreover * is
  // distributive in relation to +

  // Test (|R, +)
  // additive inverse
  close_enough( -(-a) , a );

  // additive abelian 
  close_enough( (a + b) ,  (b + a) );

  // additive associative 
  close_enough( ( (a + b) + c ) ,  ( a + (b + c) ) );

  // additive combinations
  close_enough( (a + b) ,  -((-b) - a) );
  close_enough( (a - b) ,  -(b - a) );
  close_enough( (a + (-b)) ,  (a - b));
  close_enough( ((a - b) + b) ,  a);
  close_enough( ((a + b) - b) ,  a);

  // Test (|R, *)
  // multiplicative inverse
  bigfloat_int one = bigfloat_int(1);
  close_enough( ( one / (one / a) ) , a );
 
  // multiplicative abelian 
  close_enough( (a * b) ,  (b * a));

  // multiplicative associative 
  close_enough( ( (a * b) * c ) ,  ( a * (b * c) ) );
  close_enough( ( (a / b) / c ) ,  ( a / (b * c) ) );
  close_enough( ( a / (b / c) ) ,  ( (a / b) * c ) );

  // multiplicative combinations
  close_enough( ((a * b) / b) ,  a);
  close_enough( (b * (a / b)) ,  a);
  close_enough( (a * (-b)) ,  -(a * b));
  close_enough( (a / (-b)) ,  -(a / b));

  // multiplicative distributive
  close_enough( (a * (b + c)) ,  ((a * b) + (a * c)));
  close_enough( (a * (b - c)) ,  ((a * b) - (a * c)));
  close_enough( ((a + b) / c) ,  ((a / c) + (b / c)));
}

void util_test(const bigfloat_int &a,
	       const bigfloat_int &b,
	       const bigfloat_int &c)
{
  bigfloat_int abs_b = abs(b);
  bigfloat_int abs_c = abs(c);

  // test sqrt(x)
  close_enough( sqrt(abs_c) * sqrt(abs_c) , abs_c );
  close_enough( abs_c / sqrt(abs_c) , sqrt(abs_c) );

  // test exp(x), log(x), E()
  bigfloat_int y;

  close_enough( exp(b + c) , exp(b) * exp(c) );
  close_enough( exp(-abs_c) , 1 / exp(abs_c) );
  power(y, abs_c, a);
  close_enough( exp(a * log(abs_c)) , y );
  power(y, exp(abs_c), 3);
  close_enough( exp(3 * abs_c) , y );
  power(y, E(), 3);
  close_enough( exp(bigfloat_int(3)) , y );
  close_enough( log(E()) , 1);
  close_enough( log(abs_b * abs_b) , 2 * log(abs_b) );
  close_enough( log(abs_b * abs_c) , log(abs_b) + log(abs_c) );
  close_enough( log(1/abs_b) , -log(abs_b) );
  close_enough( exp(log(E())) , E());

  // test Pi, sin(x), cos(x)
  close_enough( sin(0) , 0 );
  close_enough( sin(Pi()/2) , 1 );
  close_enough( sin(Pi()) , 0 );
  close_enough( sin(3 * (Pi()/2)) , -1 );
  close_enough( sin(2 * Pi()) , 0 );

  close_enough( sin(a + 2 * Pi()) , sin(a) );
  close_enough( sin(a + Pi()) , -sin(a) );
  close_enough( sin(Pi()/2 - a) , cos(a) );

  close_enough( cos(0) , 1 );
  close_enough( cos(Pi()/2) , 0 );
  close_enough( cos(Pi()) , -1 );
  close_enough( cos(3 * (Pi()/2)) , 0 );
  close_enough( cos(2 * Pi()) , 1 );

  close_enough( cos(a + 2 * Pi()) , cos(a) );
  close_enough( cos(a + Pi()) , -cos(a) );
  close_enough( cos(Pi()/2 - a) , sin(a) );

  // test tan(x), cot(x)
  close_enough( tan(a) , sin(a) / cos(a) );
  close_enough( tan(a + b) , (tan(a)+tan(b))/(1-tan(a)*tan(b)) );
  close_enough( cot(b) , cos(b) / sin(b) );
  close_enough( cot(2 * b) , (1-tan(b)*tan(b))/(tan(b)+tan(b)) );

  // sinh(x), cosh(x), tan(x), cot(x)
  close_enough( sinh(a + b) , cosh(a)*sinh(b)+sinh(a)*cosh(b) );
  close_enough( cosh(a + b) , cosh(a)*cosh(b)+sinh(a)*sinh(b) );
  close_enough( cosh(a)*cosh(a)-sinh(a)*sinh(a) , 1 );
  close_enough( tanh(a) , sinh(a) / cosh(a) );
  close_enough( coth(a) , cosh(a) / sinh(a) );

  // test floor(x), ceil(x), truncate(x), round(x)
  close_enough( floor(Pi()) , 3 );
  close_enough( floor(Pi()) , truncate(Pi()) );
  close_enough( round(Pi()) , truncate(Pi()) );
  close_enough( ceil(Pi()) , 4 );
  close_enough( ceil(a) , -floor(-a) );

  bigfloat_int x = 1;
  for (int i = 0; i < 10; i++)
  {
    power(y, b, i);
    close_enough(y ,  x);
    x *= b;
  }
}



void accu_test_int(const bigfloat_int &a,
	                const bigfloat_int &b,
	                const int &c)
{
  bigfloat_int x = a;
  x *= b;
  close_enough(x, (a * b));
  x += c;
  close_enough(x, ((a * b) + c));
  x -= a;
  close_enough(x, (((a * b) + c) - a));
  x /= b;
  close_enough(x, ((((a * b) + c) - a) / b));
}

void identity_test_int(const bigfloat_int &a,
		            const bigfloat_int &b,
		            const int &c)
{
  // (|R, +, *) is a field
  // (|R, +), (|R, *) are abelian groups and moreover * is
  // distributive in relation to +

  // Test (|R, +)
  // additive inverse
  close_enough( -(-a) , a );

  // additive abelian 
  close_enough( (a + b) ,  (b + a) );

  // additive associative 
  close_enough( ( (a + b) + c ) ,  ( a + (b + c) ) );

  // additive combinations
  close_enough( (a + b) ,  -((-b) - a) );
  close_enough( (a - b) ,  -(b - a) );
  close_enough( (a + (-b)) ,  (a - b));
  close_enough( ((a - b) + b) ,  a);
  close_enough( ((a + b) - b) ,  a);

  // Test (|R, *)
  // multiplicative inverse
  bigfloat_int one = bigfloat_int(1);
  close_enough( ( one / (one / a) ) , a );
 
  // multiplicative abelian 
  close_enough( (a * b) ,  (b * a));

  // multiplicative associative 
  close_enough( ( (a * b) * c ) ,  ( a * (b * c) ) );
  close_enough( ( (a / b) / c ) ,  ( a / (b * c) ) );
  close_enough( ( a / (b / c) ) ,  ( (a / b) * c ) );

  // multiplicative combinations
  close_enough( ((a * b) / b) ,  a);
  close_enough( (b * (a / b)) ,  a);
  close_enough( (a * (-b)) ,  -(a * b));
  close_enough( (a / (-b)) ,  -(a / b));

  // multiplicative distributive
  close_enough( (a * (b + c)) ,  ((a * b) + (a * c)));
  close_enough( (a * (b - c)) ,  ((a * b) - (a * c)));
  close_enough( ((a + b) / c) ,  ((a / c) + (b / c)));
}

void util_test_int(const bigfloat_int &a,
	                const bigfloat_int &b,
	                const int &c)
{
  bigfloat_int x = (bigfloat)1, y;
  int i;
  for (i = 0; i < 10; i++)
  {
    power(y, b, i);
    close_enough(y ,  x);
    x *= b;
  }

  x = (bigfloat)1;
  int abs_c = (c > 0) ? c : -c;
  for (i = 0; i < abs_c; i++)
  {
    power(y, a, i);
    close_enough(y ,  x);
    x *= a;
  }
}


void test(const bigfloat_int &a,
	  const bigfloat_int &b,
	  const bigfloat_int &c)
{
#ifdef LIDIA_DEBUG
  cout << "\ntest(" << a << ", " << b << ", " << c << ")";
#endif

#ifdef LIDIA_DEBUG
  cout << "\n    i) identity test    ";
#endif
  identity_test(a, b, c);
#ifdef LIDIA_DEBUG
  cout << "succeded.";
#endif

#ifdef LIDIA_DEBUG
  cout << "\n   ii) accumulator test ";
#endif
  accu_test(a, b, c);
#ifdef LIDIA_DEBUG
  cout << "succeded.";
#endif

#ifdef LIDIA_DEBUG
  cout << "\n  iii) utilities test   ";
#endif
  util_test(a, b, c);
#ifdef LIDIA_DEBUG
  cout << "succeded.";
#endif
}


void test_int(const bigfloat_int &a,
	           const int &b,
	           const int &c)
{
#ifdef LIDIA_DEBUG
  cout << "\ntest int(" << a << ", " << b << ", " << c << ")";
#endif

#ifdef LIDIA_DEBUG
  cout << "\n    i) identity test    ";
#endif
  identity_test_int(a, b, c);
#ifdef LIDIA_DEBUG
  cout << "succeded.";
#endif

#ifdef LIDIA_DEBUG
  cout << "\n   ii) accumulator test ";
#endif
  accu_test_int(a, b, c);
#ifdef LIDIA_DEBUG
  cout << "succeded.";
#endif

#ifdef LIDIA_DEBUG
  cout << "\n  iii) utilities test   ";
#endif
  util_test_int(a, b, c);
#ifdef LIDIA_DEBUG
  cout << "succeded.";
#endif
}


main()
{
  bigfloat::set_precision(40);
  
  bigfloat_int a = bigfloat_int(1);
  bigfloat_int b = bigfloat_int(1) / 13;
  bigfloat_int c = bigfloat_int(7);

  test(a, a, a);
  test(b, b, b);
  test(c, c, c);
  test(a, b, c);
  test(b, c, a);
  test(c, a, b);

  a = bigfloat_int(-2.625);
  b = bigfloat_int(4.0);
  c = bigfloat_int(-6.5);

  test(a, a, a);
  test(b, b, b);
  test(c, c, c);
  test(a, b, c);
  test(b, c, a);
  test(c, a, b);

  a = bigfloat_int(-0.5);
  b = bigfloat_int(-1.0);
  c = bigfloat_int(0.5);

  test(a, a, a);
  test(b, b, b);
  test(c, c, c);
  test(a, b, c);
  test(b, c, a);
  test(c, a, b);

  int f = -48;
  int g = 6;

  test_int(a, f, g);
  test_int(b, f, g);
  test_int(c, f, g);
  test_int(a, g, f);
  test_int(b, g, f);
  test_int(c, g, f);
  test_int(a, f, f);
  test_int(b, f, f);
  test_int(c, f, f);
  test_int(a, g, g);
  test_int(b, g, g);
  test_int(c, g, g);

  f /= 6;
  g /= 3;

  test(a, f, g);
  test(b, f, g);
  test(c, f, g);
  test_int(a, f, g);
  test_int(b, f, g);
  test_int(c, f, g);
  test(a, g, f);
  test(b, g, f);
  test(c, g, f);
  test_int(a, g, f);
  test_int(b, g, f);
  test_int(c, g, f);

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