//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : bigcomplex.c 
// Author      : Thomas Papanikolaou (TP)
// Last change : TP, Feb  7 1995, initial version
//		 MM, Nov 21 1996, add "is_real()"
//				  and "is_imaginary()"
//

#include <LiDIA/bigcomplex.h>

/**
 ** Constructors and destructor
 **/

bigcomplex::bigcomplex() 
{ }

/*
bigcomplex::bigcomplex(const bigcomplex& y) :re(y.re), im(y.im) 
{ }
bigcomplex::bigcomplex(const bigfloat& y) :re(y), im(0)
{ }
bigcomplex::bigcomplex(const bigfloat &r, const bigfloat &i) :re(r), im(i) 
{ }
*/

bigcomplex::bigcomplex(const bigcomplex& y)
{ re.assign(y.re); im.assign(y.im); }

bigcomplex::bigcomplex(const bigfloat& y)
{ re.assign(y); im.assign_exact_zero(); }

bigcomplex::bigcomplex(const bigfloat &r, const bigfloat &i)
{ re.assign(r); im.assign(i); }

bigcomplex::~bigcomplex() 
{ }

/**
 ** Member functions
 **/

bigfloat bigcomplex::real() const
{ return re; }

bigfloat bigcomplex::imag() const
{ return im; }

bool bigcomplex::is_zero() const
{ if (re.is_zero() && im.is_zero()) return true;
  return false; }

bool bigcomplex::is_real() const
{ return (im.is_zero()); }

bool bigcomplex::is_imaginary() const
{ return (re.is_zero()); }

void bigcomplex::negate()
{ re.negate(); im.negate(); }

void bigcomplex::invert()
{
  if (re.is_zero() && im.is_zero()) 
    lidia_error_handler("bigcomplex", "invert::division by zero.");

  bigfloat inv_den, tmp1;

  square(tmp1, re);
  square(inv_den, im);
  add(tmp1, tmp1, inv_den);
  divide(inv_den, 1, tmp1);

  multiply(re, re, inv_den);
  im.negate();
  multiply(im, im, inv_den);
}

void bigcomplex::assign_zero()
{ 
  re.assign_zero();
  im.assign_zero();
}

void bigcomplex::assign_one()
{ 
  re.assign_one();
  im.assign_zero();
}

void bigcomplex::assign(const bigfloat &x)
{ 
  re.assign(x);
  im.assign_zero();
}

void bigcomplex::assign(const bigfloat &x, const bigfloat &y)
{ 
  re.assign(x);
  im.assign(y);
}

void bigcomplex::assign(const bigcomplex &x)
{ 
  re.assign(x.re);
  im.assign(x.im);
}

/**
 ** Precision and rounding mode setting
 **/

void bigcomplex::precision(long l)
{ bigfloat::precision(l); }

void bigcomplex::mode(int l)
{ bigfloat::mode(l); }

/**
 ** Type checking
 **/

bool is_bigfloat(const bigcomplex & a)
{ if (a.im.is_zero()) return true;
  return false; }


/**
 ** Assignments
 **/

bigfloat  bigcomplex::operator = (const bigfloat& y) 
{ this->assign(y); return y; } 

bigcomplex&  bigcomplex::operator = (const bigcomplex& y) 
{ this->assign(y); return *this; } 


/**
 ** Comparisons
 **/

bool  operator == (const bigcomplex& x, const bigcomplex& y)
{ if ((x.re.compare(y.re) == 0) && (x.im.compare(y.im) == 0)) return true;
  return false; }

bool  operator != (const bigcomplex& x, const bigcomplex& y)
{ if ((x.re.compare(y.re) != 0) || (x.im.compare(y.im) != 0)) return true;
  return false; }

bool  operator == (const bigcomplex& x, const bigfloat& y)
{ if (x.im.is_zero() && (x.re.compare(y) == 0)) return true;
  return false; }

bool  operator != (const bigcomplex& x, const bigfloat& y)
{ if (!x.im.is_zero() || (x.re.compare(y) != 0)) return true;
  return false; }

bool  operator == (const bigfloat& x, const bigcomplex& y)
{ if (y.im.is_zero() && (y.re.compare(x) == 0)) return true;
  return false; }

bool  operator != (const bigfloat& x, const bigcomplex& y)
{ if (!y.im.is_zero() || (y.re.compare(x) != 0)) return true;
  return false; }


/**
 ** Operator overloading
 **/

bigcomplex  operator - (const bigcomplex& x)
{ 
  bigcomplex c; 
  negate(c, x); 
  return c; 
} 

bigcomplex  operator + (const bigcomplex& x, const bigcomplex &y)
{ 
  bigcomplex c; 
  add(c, x, y);
  return c; 
} 

bigcomplex  operator - (const bigcomplex& x, const bigcomplex &y)
{ 
  bigcomplex c; 
  subtract(c, x, y);
  return c; 
} 

bigcomplex  operator * (const bigcomplex& x, const bigcomplex& y)
{
  bigcomplex c;
  multiply(c, x, y);
  return c;
}

bigcomplex operator / (const bigcomplex& x, const bigcomplex& y)
{
  bigcomplex c;
  divide(c, x, y);
  return c;
}

bigcomplex  operator + (const bigcomplex& x, const bigfloat &y)
{ 
  bigcomplex c; 
  add(c, x, y); 
  return c; 
} 

bigcomplex  operator - (const bigcomplex& x, const bigfloat &y)
{ 
  bigcomplex c; 
  subtract(c, x, y); 
  return c; 
} 

bigcomplex  operator * (const bigcomplex& x, const bigfloat &y)
{
  bigcomplex c;
  multiply(c, x, y); 
  return c;
}

bigcomplex operator / (const bigcomplex &x, const bigfloat &y)
{
  bigcomplex c;
  divide(c, x, y); 
  return c;
}

bigcomplex  operator + (const bigfloat &x, const bigcomplex& y)
{ 
  bigcomplex c; 
  add(c, x, y); 
  return c;
} 

bigcomplex  operator - (const bigfloat &x, const bigcomplex& y)
{ 
  bigcomplex c; 
  subtract(c, x, y); 
  return c; 
} 

bigcomplex  operator * (const bigfloat &x, const bigcomplex& y)
{
  bigcomplex c;
  multiply(c, x, y);
  return c;
}

bigcomplex operator / (const bigfloat &x, const bigcomplex& y)
{
  bigcomplex c;
  divide(c, x, y);
  return c;
}

bigcomplex&  bigcomplex::operator += (const bigcomplex& y)
{ 
  add(*this, *this, y); 
  return *this; 
}

bigcomplex&  bigcomplex::operator -= (const bigcomplex& y)
{ 
  subtract(*this, *this, y); 
  return *this; 
}

bigcomplex&  bigcomplex::operator *= (const bigcomplex& y)
{ 
  multiply(*this, *this, y); 
  return *this; 
}

bigcomplex& bigcomplex::operator /= (const bigcomplex& y)
{
  divide(*this, *this, y); 
  return *this;
}

bigcomplex&  bigcomplex::operator += (const bigfloat &y)
{ 
  add(*this, *this, y); 
  return *this; 
} 

bigcomplex&  bigcomplex::operator -= (const bigfloat &y)
{ 
  subtract(*this, *this, y); 
  return *this; 
} 

bigcomplex&  bigcomplex::operator *= (const bigfloat &y)
{ 
  multiply(*this, *this, y); 
  return *this; 
} 

bigcomplex& bigcomplex::operator /= (const bigfloat &y)
{
  divide(*this, *this, y); 
  return *this;
}

/**
 ** Procedural versions
 **/

// (cr, ci) = (-xr, -xi)
void negate(bigcomplex &c, const bigcomplex &x)
{ 
  c.re.assign(x.re); 
  c.re.negate(); 
  c.im.assign(x.im); 
  c.im.negate(); 
}

// (cr, ci) = (xr, xi) + (yr, yi) = (xr+yr, xi+yi)
void add(bigcomplex &c, const bigcomplex &x, const bigcomplex &y)
{
  add(c.re, x.re, y.re); 
  add(c.im, x.im, y.im); 
}

// (cr, ci) = (xr, xi) - (yr, yi) = (xr-yr, xi-yi)
void subtract(bigcomplex &c, const bigcomplex &x, const bigcomplex &y)
{
  subtract(c.re, x.re, y.re); 
  subtract(c.im, x.im, y.im); 
}

// (cr, ci) = (xr, xi) * (yr, yi)
//          = (xr*yr - xi*yi, xr*yi + xi*yr)
void multiply(bigcomplex &c, const bigcomplex &x, const bigcomplex &y)
{
  bigfloat tmp, tmpr, tmpi;

  multiply(tmpr, x.re, y.re);
  multiply(tmpi, x.im, y.im);
  subtract(tmp, tmpr, tmpi);

  multiply(tmpr, x.re, y.im);
  multiply(tmpi, x.im, y.re);
  add(c.im, tmpr, tmpi);
  c.re.assign(tmp);
}

/* from romine@xagsun.epm.ornl.gov */
// (cr, ci) = (xr, xi) / (yr, yi)
void divide(bigcomplex &c, const bigcomplex &x, const bigcomplex &y)
{
  bigfloat den = abs(y.re) + abs(y.im);
  if (den.is_zero()) 
    lidia_error_handler("bigcomplex", "operator/::division by zero.");

  bigfloat inv_den, xrden, xiden, yrden, yiden;;
  divide(inv_den, 1, den);

  multiply(xrden, x.re, inv_den);
  multiply(xiden, x.im, inv_den);
  multiply(yrden, y.re, inv_den);
  multiply(yiden, y.im, inv_den);

  bigfloat inv_nrm, tmp1, tmp2;

  square(tmp1, yrden);
  square(tmp2, yiden);
  add(tmp1, tmp1, tmp2);
  divide(inv_nrm, 1, tmp1);

  multiply(tmp1, xrden, yrden);
  multiply(tmp2, xiden, yiden);
  add(tmp1, tmp1, tmp2);
  multiply(c.re, tmp1, inv_nrm);
 
  multiply(tmp1, xiden, yrden);
  multiply(tmp2, xrden, yiden);
  subtract(tmp1, tmp1, tmp2);
  multiply(c.im, tmp1, inv_nrm);
 
}

// (cr, ci) = (xr, xi) + y = (xr+y, xi)
void add(bigcomplex &c, const bigcomplex &x, const bigfloat &y)
{
  add(c.re, x.re, y);
  c.im.assign(x.im);
}

// (cr, ci) = (xr, xi) - y = (xr-y, xi)
void subtract(bigcomplex &c, const bigcomplex &x, const bigfloat &y)
{
  subtract(c.re, x.re, y);
  c.im.assign(x.im);
}

// (cr, ci) = (xr, xi) * y = (xr*y, xi*y)
void multiply(bigcomplex &c, const bigcomplex &x, const bigfloat &y)
{
  multiply(c.re, x.re, y);
  multiply(c.im, x.im, y);
}

// (cr, ci) = (xr, xi) / y = (xr/y, xi/y)
void divide(bigcomplex &c, const bigcomplex &x, const bigfloat &y)
{
  if (y.is_zero()) 
    lidia_error_handler("bigcomplex", "operator/3::division by zero.");
  divide(c.re, x.re, y);
  divide(c.im, x.im, y);
}

void add(bigcomplex &c, const bigfloat &x, const bigcomplex &y)
{
  add(c.re, x, y.re);
  c.im.assign(y.im);
}

void subtract(bigcomplex &c, const bigfloat &x, const bigcomplex &y)
{
  subtract(c.re, y.re, x);
  c.re.negate();
  c.im.assign(y.im);
  c.im.negate();
}


void multiply(bigcomplex &c, const bigfloat &x, const bigcomplex &y)
{
  multiply(c.re, y.re, x);
  multiply(c.im, y.im, x);
}

void divide(bigcomplex &c, const bigfloat &x, const bigcomplex &y)
{
  if (y.re.is_zero() && y.im.is_zero()) 
    lidia_error_handler("bigcomplex", "operator/2::division by zero.");

  bigfloat inv_den, tmp1, tmp2;

  square(tmp1, y.re);
  square(tmp2, y.im);
  add(tmp1, tmp1, tmp2);
  divide(inv_den, 1, tmp1);

  multiply(c.re, y.re, x);
  multiply(c.re, c.re, inv_den);

  multiply(c.im, y.im, x);
  multiply(c.im, c.im, inv_den);
  c.im.negate();
}

/**
 ** functions
 **/

// (cr, ci) = ~(xr, xi) = (xr, -xi)
void conj(bigcomplex & c, const bigcomplex & x)
{ 
  c.assign(x); 
  c.im.negate(); 
}

// (cr, ci) = (r * cos(t), r * sin(t))
void polar(bigcomplex & c, const bigfloat & r, const bigfloat & t)
{ 
  bigfloat tmp;
  cos(tmp, t);
  multiply(c.re, r, tmp);
  sin(tmp, t);
  multiply(c.im, r, tmp);
}

void cos(bigcomplex & c, const bigcomplex & x)
{
  bigfloat tmp;
  cos(c.re, x.re);
  cosh(tmp, x.im);
  multiply(c.re, c.re, tmp);
  sin(c.im, x.re);
  sinh(tmp, x.im);
  multiply(c.im, c.im, tmp);
  c.im.negate();
}

void sin(bigcomplex & c, const bigcomplex & x)
{
  bigfloat tmp;
  sin(c.re, x.re);
  cosh(tmp, x.im);
  multiply(c.re, c.re, tmp);
  cos(c.im, x.re);
  sinh(tmp, x.im);
  multiply(c.im, c.im, tmp);
}

void cosh(bigcomplex & c, const bigcomplex & x)
{
  bigfloat tmp;
  cos(c.re, x.im);
  cosh(tmp, x.re);
  multiply(c.re, c.re, tmp);
  sin(c.im, x.im);
  sinh(tmp, x.re);
  multiply(c.im, c.im, tmp);
}

void sinh(bigcomplex & c, const bigcomplex & x)
{
  bigfloat tmp;
  cos(c.re, x.im);
  sinh(tmp, x.re);
  multiply(c.re, c.re, tmp);
  sin(c.im, x.im);
  cosh(tmp, x.re);
  multiply(c.im, c.im, tmp);
}

void sqrt(bigcomplex & c, const bigcomplex & x)
{
  if (x.re.is_zero() && x.im.is_zero())
  {
    c.re.assign_zero();
    c.im.assign_zero();
  }
  else
  {
    bigfloat s, t, d;

    s = hypot(x);
    t.assign(x.re);
    t.absolute_value();
    add(t, t, s);
    t.divide_by_2();
    sqrt(s, t);

    divide(d, x.im, s);
    d.divide_by_2();

    if (x.re.is_gt_zero())
    {
      c.re.assign(s);
      c.im.assign(d);
    }
    else if (x.im.is_ge_zero())
    {
      c.re.assign(d);
      c.im.assign(s);
    }
    else
    {
      d.negate();
      s.negate();
      c.re.assign(d);
      c.im.assign(s);
    }
  }
}

void exp(bigcomplex & c, const bigcomplex & x)
{
  bigfloat tmp;
  exp(tmp, x.re);
  cos(c.re, x.im);
  multiply(c.re, c.re, tmp);
  sin(c.im, x.im);
  multiply(c.im, c.im, tmp);
}

void log(bigcomplex & c, const bigcomplex & x)
{
  bigfloat h = hypot(x);
  if (h.is_le_zero()) 
    lidia_error_handler("bigcomplex", "log of number less equal zero");
  log(c.re, h);
  atan2(c.im, x.im, x.re);
}

// Corrections based on reports from: thc@cs.brown.edu&saito@sdr.slb.com
void power(bigcomplex & c, const bigcomplex & x, const bigcomplex & p)
{
  bigfloat h = hypot(x);
  if (h.is_le_zero()) 
    lidia_error_handler("bigcomplex", "power of number less equal zero");
  bigfloat a, lr, li;
  atan2(a, x.im, x.re);
  power(lr, h, p.re);
  multiply(li, p.re, a);
  if (!p.im.is_zero())
  {
    bigfloat tmp;
    multiply(tmp, p.im, a);
    exp(tmp, tmp);
    divide(lr, lr, tmp);
    log(tmp, h);
    multiply(tmp, tmp, p.im);
    add(li, li, tmp);
  }
  cos(c.re, li);
  multiply(c.re, c.re, lr);
  sin(c.im, li);
  multiply(c.im, c.im, lr);
}

void power(bigcomplex & c, const bigcomplex & x, const bigfloat & p)
{
  bigfloat h = hypot(x);
  if (h.is_le_zero()) 
    lidia_error_handler("bigcomplex", "power of number less equal zero");
  bigfloat lr, a;
  power(lr, h, p);
  atan2(a, x.im, x.re);
  bigfloat li;
  multiply(li, p, a);
  cos(c.re, li);
  multiply(c.re, c.re, lr);
  sin(c.im, li);
  multiply(c.im, c.im, lr);
}

void power(bigcomplex & c, const bigcomplex & x, long p)
{
  if (p == 0)
  {
    c.re.assign_one();
    c.im.assign_zero();
  }
  else if (x.is_zero())
  {
    c.re.assign_zero();
    c.im.assign_zero();
  }
  else
  {
    c = bigcomplex(1.0, 0.0);
    bigcomplex b(x);
    if (p < 0)
    {
      p = -p;
      b = bigfloat(1.0) / b;
    }
    for(;;)
    {
      if (p & 1)
        c *= b;
      if ((p >>= 1) == 0)
        break;
      else
        b *= b;
    }
  }
}

void invert(bigcomplex &c, const bigcomplex &y)
{ c.assign(y); c.invert(); }

void swap(bigcomplex &c, bigcomplex &y)
{ 
  bigfloat tmp(c.re);
  c.re.assign(y.re);
  y.re.assign(tmp);
  tmp.assign(c.im);
  c.im.assign(y.im);
  y.im.assign(tmp);
}

void square(bigcomplex &c, const bigcomplex &y)
{ 
  bigfloat re2, im2, reim;
  square(re2, y.re);
  square(im2, y.im);
  multiply(reim, y.re, y.im);
  subtract(c.re, re2, im2);
  c.im.assign(reim);
  c.im.multiply_by_2();
}


bigcomplex  conj(const bigcomplex& x)
{ 
  bigcomplex c; 
  conj(c, x); 
  return c; 
}

bigcomplex  polar(const bigfloat &r, const bigfloat &t)
{ 
  bigcomplex c;
  polar(c, r, t);
  return c;
}

bigcomplex cos(const bigcomplex& x)
{
  bigcomplex c;
  cos(c, x);
  return c;
}

bigcomplex sin(const bigcomplex& x)
{
  bigcomplex c;
  sin(c, x);
  return c;
}

bigcomplex cosh(const bigcomplex& x)
{
  bigcomplex c;
  cosh(c, x);
  return c;
}

bigcomplex sinh(const bigcomplex& x)
{
  bigcomplex c;
  sinh(c, x);
  return c;
}

bigcomplex sqrt(const bigcomplex& x)
{
  bigcomplex c;
  sqrt(c, x);
  return c;
}

bigcomplex exp(const bigcomplex& x)
{
  bigcomplex c;
  exp(c, x);
  return c;
}

bigcomplex log(const bigcomplex& x)
{
  bigcomplex c;
  log(c, x);
  return c;
}

bigcomplex power(const bigcomplex& x, const bigcomplex& p)
{
  bigcomplex c;
  power(c, x, p);
  return c; 
}

bigcomplex power(const bigcomplex& x, const bigfloat &p)
{
  bigcomplex c;
  power(c, x, p);
  return c; 
}

bigcomplex power(const bigcomplex& x, long p)
{
  bigcomplex c;
  power(c, x, p);
  return c; 
}

bigcomplex inverse(const bigcomplex& x)
{
  bigcomplex c;
  invert(c, x);
  return c; 
}

bigcomplex square(const bigcomplex& x)
{
  bigcomplex c;
  square(c, x);
  return c; 
}

bigfloat real(const bigcomplex& x)
{ return x.re; }

bigfloat imag(const bigcomplex& x)
{ return x.im; }

bigfloat  abs(const bigcomplex& x)
{ 
  bigfloat c, tmp; 
  square(c, x.re); 
  square(tmp, x.im);
  add(c, c, tmp);
  sqrt(c, c);
  return c;
}

bigfloat  norm(const bigcomplex& x)
{ 
  bigfloat c, tmp; 
  square(c, x.re); 
  square(tmp, x.im);
  add(c, c, tmp);
  return c;
}

bigfloat  hypot(const bigcomplex& x)
{ 
  bigfloat c, tmp;
  square(c, x.re); 
  square(tmp, x.im);
  add(c, c, tmp);
  sqrt(c, c);
  return c;
}

bigfloat  arg(const bigcomplex& x)
{ bigfloat c; atan2(c, x.im, x.re); return c; }

/**
 ** Input/Output
 **/

istream& operator >> (istream& s, bigcomplex& x)
{
  bigfloat r(0), i(0);
  char ch;
  
  s.get(ch);
  if (ch == '(')
  {
    s >> r;
    s.get(ch);
    if (ch == ',')
    {
      s >> i;
      s.get(ch);
    }
    else
      i = 0;
    if (ch != ')')
      s.clear(ios::failbit);
  }
  else
  {
    s.putback(ch);
    s >> r;
    i = 0;
  }
  x = bigcomplex(r, i);
  return s;
}

ostream& operator << (ostream& s, const bigcomplex& x)
{
  return s << "(" << x.real() << "," << x.imag() << ")" ;
}









