//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : qi_class_real.C 
// Author      : Michael Jacobson, Jr. (MJ)
// Last change : MJ, September 24, 1996
//

#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:quadratic_order.h>
#else
#include <LiDIA/quadratic_order.h>
#endif


void
qi_class_real::reduce()
{
  bigint a2,q,temp,BB,OB,NB;
  bigint AA,OA,NA;
  bigfloat a02,b0,temp2,temp3,dmod;

  normalize_real();

  shift_left(a02,bigfloat(a),1);
  b0.assign(b);

  OB.assign_zero();
  BB.assign_one();
  OA.assign_one();
  AA.assign_zero();

  shift_left(temp,a,1);
  subtract(temp,rootD,temp);
  if (temp.is_negative())
    inc(temp);
  temp.absolute_value();

  while ((!((temp.compare(b) < 0) && (b.compare(rootD) <= 0))) || (a.is_lt_zero())) {
    /* a = (D - b^2) / 4a */
    shift_left(a2,a,2);
    square(temp,b);
    subtract(temp,Delta,temp);
    divide(a,temp,a2);

    if (a <= rootD) {
      /* q = floor((b + rootD) / 2a) */
      shift_left(a2,a,1);
      add(q,rootD,b);
      temp2.assign(q,a2);
      temp3 = floor(temp2);
      temp3.bigintify(q);
    }
    else {
      /* q = floor((a + rootD) / 2a) */
      shift_left(a2,a,1);
      add(q,a,b);
      temp2.assign(q,a2);
      temp3 = floor(temp2);
      temp3.bigintify(q);
    }

    /* b = 2aq - b */
    multiply(temp,a2,q);
    subtract(b,temp,b);

    shift_left(temp,abs(a),1);
    subtract(temp,rootD,temp);
    if (temp.is_negative())
      inc(temp);
    temp.absolute_value();

    multiply(NB,BB,q);
    add(NB,NB,OB);
    OB.assign(BB);
    BB.assign(NB);

    multiply(NA,AA,q);
    add(NA,NA,OA);
    OA.assign(AA);
    AA.assign(NA);
  }
  
  add(temp2,b0,rd);
  divide(temp2,temp2,a02);
  multiply(temp2,temp2,bigfloat(OB));
  add(dmod,temp2,bigfloat(OA));
  add(d,d,log(abs(dmod)));
}




qi_class_real::qi_class_real()
{
  d.assign_zero();
}



qi_class_real::qi_class_real(const quadratic_form & qf) : qi_class(qf)
{
  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","qi_class_real() - the current \
quadratic order is not real");

  d.assign_zero();
}



qi_class_real::qi_class_real(const quadratic_ideal & A)
{
  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","qi_class_real() - the current \
quadratic order is not real");

  a.assign(A.get_a());
  b.assign(A.get_b());
  log(d,bigfloat(A.get_q()));
  reduce();
}



qi_class_real::qi_class_real(const qi_class & A) : qi_class(A)
{
  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","qi_class_real() - the current \
quadratic order is not real");

  d.assign_zero();
}



qi_class_real::qi_class_real(const qi_class_real & A)
{
  a.assign(A.a);
  b.assign(A.b);
  d.assign(A.d);
}



qi_class_real::~qi_class_real()
{
}



void
qi_class_real::assign_zero()
{
  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign_zero() - the current \
quadratic order is not real");

  a.assign_zero();
  b.assign_zero();
  d.assign_zero();
}



void
qi_class_real::assign_one()
{
  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign_one() - the current \
quadratic order is not real");

  a.assign_one();
  b.assign(Delta);
  normalize();
  d.assign_zero();
}



void
qi_class_real::assign_principal(const bigint & x, const bigint & y)
{
  bigint x2,y2,n,m,k,l,temp,temp2;

  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign_principal() - the current \
quadratic order is not real");

  /* use (x + y \sqrt(Delta))/2 form */
  y2.assign(y);
  shift_left(x2,x,1);
  if (Delta.is_odd())
    add(x2,x2,y);

  /* compute norm of (x2 + y2 sqrt(Delta))/2 */
  square(n,x2);
  square(temp,y2);
  multiply(temp,temp,Delta);
  subtract(n,n,temp);
  shift_right(n,n,2);
  n.absolute_value();

  /* solve m = k y2 + l (x2 + y2 Delta) / 2 */
  multiply(temp2,y2,Delta);
  add(temp2,temp2,x2);
  temp2.divide_by_2();
  m.assign(xgcd(k,l,y2,temp2));

  /* a = abs(n / m^2) */
  square(temp,m);
  divide(a,n,temp);

  /* (b = kx2 + l(x2+y2)D/2 ) / m */
  add(temp2,x2,y2);
  multiply(temp2,temp2,l);
  multiply(b,temp2,Delta);
  b.divide_by_2();
  multiply(temp,k,x2);
  add(b,b,temp);
  divide(b,b,m);
  shift_left(temp,a,1);
  remainder(b,b,temp);

  d.assign_zero();
  reduce();
}



bool
qi_class_real::assign(const bigint & a2, const bigint & b2)
{
  bigint c,temp;

  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign() - the current quadratic \
order is not real");

  d.assign_zero();

  if (a2.is_zero())
    if (b2.is_zero()) {
      a.assign_zero();
      b.assign_zero();
      return true;
    }
    else
      return false;
  else {
    square(c,b2);
    subtract(c,c,Delta);
    shift_left(temp,a2,2);
    remainder(c,c,temp);
    if (c.is_zero()) {
      a.assign(a2);
      b.assign(b2);
      reduce();

      if (is_invertible())
        return true;
      else {
        a.assign_zero();
        b.assign_zero();
        d.assign_zero();
        return false;
      }
    }
    else
      return false;
  }
}



bool
qi_class_real::assign(const long a2, const long b2)
{
  bigint c,temp;

  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign() - the current quadratic \
order is not real");

  d.assign_zero();

  if (!a2)
    if (!b2) {
      a.assign_zero();
      b.assign_zero();
      return true;
    }
    else
      return false;
  else {
    square(c,b2);
    subtract(c,c,Delta);
    shift_left(temp,a2,2);
    remainder(c,c,temp);
    if (c.is_zero()) {
      a.assign(a2);
      b.assign(b2);
      reduce();

      if (is_invertible())
        return true;
      else {
        a.assign_zero();
        b.assign_zero();
        d.assign_zero();
        return false;
      }
    }
    else
      return false;
  }
}



void
qi_class_real::assign(const quadratic_form & qf)
{
  if (qf.discriminant() != Delta)
    lidia_error_handler("qi_class_real","assign() - the quadratic form has a \
different discriminant");

  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign() - the current quadratic \
order is not real");

  d.assign_zero();
  a.assign(abs(qf.get_a()));
  b.assign(qf.get_b());
  reduce();

  if (!is_invertible())
    lidia_error_handler("qi_class_real","assign() - the quadratic form is not \
invertible");
}



void
qi_class_real::assign(const quadratic_ideal & B)
{
  if (B.discriminant() != Delta)
    lidia_error_handler("qi_class_real","assign() - the quadratic ideal \
belongs to different quadratic order");

  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign() - the current quadratic \
order is not real");

  a.assign(B.get_a());
  b.assign(B.get_b());
  log(d,bigfloat(B.get_q()));
  reduce();

  if (!is_invertible())
    lidia_error_handler("qi_class_real","assign() - the quadratic ideal is \
not invertible");
}



void
qi_class_real::assign(const qi_class & B)
{
  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class_real","assign() - the current quadratic \
order is not real");

  d.assign_zero();
  a.assign(B.get_a());
  b.assign(B.get_b());
}



void
qi_class_real::assign_principal(const bigint & x, const bigint & y, const bigfloat & dist)
{
  assign_principal(x,y);
  d.assign(dist);
}



bool
qi_class_real::assign(const bigint & a2, const bigint & b2, const bigfloat & dist)
{
  bool OK;

  OK = assign(a2,b2);
  if (OK)
    d.assign(dist);

  return OK;
}



bool
qi_class_real::assign(const long a2, const long b2, const bigfloat & dist)
{
  bool OK;

  OK = assign(a2,b2);
  if (OK)
    d.assign(dist);

  return OK;
}



void
qi_class_real::assign(const quadratic_form & qf, const bigfloat & dist)
{
  assign(qf);
  d.assign(dist);
}



void
qi_class_real::assign(const quadratic_ideal & B, const bigfloat & dist)
{
  assign(B);
  d.assign(dist);
}



void
qi_class_real::assign(const qi_class & B, const bigfloat & dist)
{
  assign(B);
  d.assign(dist);
}



void
qi_class_real::assign(const qi_class_real & B)
{
  a.assign(B.a);
  b.assign(B.b);
  d.assign(B.d);
}



qi_class_real & qi_class_real::operator = (const qi_class_real & A)
{
  assign(A);
  return *this;
}



void
multiply(qi_class_real & C, const qi_class_real & A, const qi_class_real & B)
{
  multiply_real(C,A,B);
}



void
multiply_real(qi_class_real & C, const qi_class_real & A, const qi_class_real & B)
{
  bigint newa,newb,gpr,v,g,w,ab2,temp;

  /* solve gpr = v A.a + w B.a */
  gpr.assign(xgcd_left(v,A.a,B.a));

  /* C.b = v A.a (B.b - A.b) */
  multiply(newb,v,A.a);
  subtract(temp,B.b,A.b);
  multiply(newb,newb,temp);

  /* C.a = A.a B.a */
  multiply(newa,A.a,B.a);

  if (!gpr.is_one()) {
    add(ab2,A.b,B.b);
    ab2.divide_by_2();
    g.assign(xgcd(v,w,gpr,ab2));

    /* C.b = (C.b*v + w(D - A.b^2)/2) / d */
    multiply(newb,newb,v);

    square(temp,A.b);
    subtract(temp,qi_class_real::discriminant(),temp);
    temp.divide_by_2();
    multiply(temp,temp,w);

    add(newb,newb,temp);
    divide(newb,newb,g);

    /* C.a = C.a / (g^2) */
    square(temp,g);
    divide(newa,newa,temp);

    gpr.assign(g);
  }

  C.a.assign(newa);
  add(newb,newb,A.b);
  shift_left(ab2,C.a,1);
  remainder(C.b,newb,ab2);

  add(C.d,A.d,B.d);
  subtract(C.d,C.d,log(bigfloat(gpr)));

  C.reduce();
}




void
qi_class_real::invert()
{
  b.negate();
  normalize_real();
  d.negate();
  add(d,d,log(bigfloat(a)));
}



void
inverse(qi_class_real & A, const qi_class_real & B)
{
  A.assign(B);
  A.b.negate();
  A.normalize_real();
  A.d.negate();
  add(A.d,A.d,log(bigfloat(A.a)));
}



qi_class_real
inverse(const qi_class_real & B)
{
  qi_class_real A;

  A.assign(B);
  A.b.negate();
  A.normalize_real();
  A.d.negate();
  add(A.d,A.d,log(bigfloat(A.a)));

  return A;
}




void
divide(qi_class_real & C, const qi_class_real & A, const qi_class_real & B)
{
  qi_class_real temp;

  if (B.is_zero())
    lidia_error_handler("qi_class_real","divide() - zero divisor");

  temp = inverse(B);
  multiply_real(C,A,temp);
}



void
square(qi_class_real & C, const qi_class_real & A)
{
  square_real(C,A);
}



void
square_real(qi_class_real & C, const qi_class_real & A)
{
  bigint newb,g,w,temp;

  /* solve g = v A.a + w A.b */
  g.assign(xgcd_right(w,A.a,A.b));

  /* C.b = A.b + w (D - A.b^2) / (2d) */
  square(newb,A.b);
  subtract(newb,qi_class_real::discriminant(),newb);
  shift_left(temp,g,1);
  divide(newb,newb,temp);
  multiply(newb,newb,w);
  add(C.b,newb,A.b);

  /* C.a = (A.a/g)^2 */
  divide(temp,A.a,g);
  square(C.a,temp);

  shift_left(temp,C.a,1);
  remainder(C.b,C.b,temp);

  C.d.assign(A.d);
  C.d.multiply_by_2();
  subtract(C.d,C.d,log(bigfloat(g)));

  C.reduce();
}



void
power(qi_class_real & C, const qi_class_real & A, const bigint & i)
{
  power_real(C,A,i);
}



void
power(qi_class_real & C, const qi_class_real & A, const long i)
{
  power_real(C,A,i);
}



void
power_real(qi_class_real & C, const qi_class_real & A, const bigint & i)
{
  qi_class_real B;
  bigint j;

  B.assign(A);
  j.assign(i);
  if (j.is_lt_zero()) {
    B.invert();
    j.absolute_value();
  }
  C.assign_one(); 
  while (j.is_gt_zero()) {
    if (j.is_odd())
      multiply_real(C,C,B);
    j.divide_by_2();
    if (j.is_gt_zero())
      square_real(B,B);
  }
}



void
power_real(qi_class_real & C, const qi_class_real & A, const long i)
{
  qi_class_real B;
  register long j;

  B.assign(A);
  j = i;
  if (j < 0) {
    B.invert();
    j = -i;
  }
  C.assign_one();
  while (j > 0) {
    if ((j & 1) == 1)
      multiply_real(C,C,B);
    j >>= 1;
    if (j > 0)
      square_real(B,B);
  }
}



qi_class_real
nearest(const qi_class_real & S, const bigfloat & E)
{
  long j,k;
  bigfloat EK,temp,temp2;
  qi_class_real A,B;

  if (E.is_zero())
    return S;

  /* compute k such that 2^k < E < 2^(k+1) */
  divide(temp,log(abs(E)),log(bigfloat(2)));
  floor(temp2,temp);
  temp2.longify(k);

  /* EK = E / 2^k */
  shift_right(EK,E,k);

  /* compute B with distance < EK */
  B.assign_one();
  if (EK.is_gt_zero()) {
    while (B.d <= EK)
      B.rho();
    B.inverse_rho();
  }
  else {
    while (B.d >= EK)
      B.inverse_rho();
    B.rho();
  }

  for (j=0; j<k; ++j) {
    EK.multiply_by_2();
    square(B,B);
    if (EK.is_gt_zero()) {
      while (B.d <= EK)
        B.rho();
      while (B.d > EK)
        B.inverse_rho();
    }
    else {
      while (B.d >= EK)
        B.inverse_rho();
      B.rho();
      while (B.d < EK)
        B.rho();
    }
  }

  if (S.is_one()) 
    A.assign(B);
  else {
    A.assign(S,0.0);
    multiply_real(A,A,B);
    if (E > 0) {
      while (A.d <= E)
        A.rho();
      while (A.d > E)
        A.inverse_rho();
    }
    else {
      while (A.d >= E)
        A.inverse_rho();
      while (A.d < E)
        A.rho();
    }
  }
 
  temp = abs(A.d - E);
  apply_rho(B,A);
  temp2 = abs(B.d - E);
  if (temp2 < temp)
    A.assign(B);
  else {
    apply_inverse_rho(B,A);
    temp2 = abs(B.d - E);
    if (temp2 < temp)
      A.assign(B);
  }

  return A;
}



qi_class_real operator - (const qi_class_real & A)
{
  qi_class_real B;

  B = inverse(A);
  return B;
}




qi_class_real operator * (const qi_class_real & A, const qi_class_real & B)
{
  qi_class_real C;

  multiply_real(C,A,B);
  return C;
}



qi_class_real operator / (const qi_class_real & A, const qi_class_real & B)
{
  qi_class_real C;

  divide(C,A,B);
  return C;
}



qi_class_real & qi_class_real::operator *= (const qi_class_real & A)
{
  multiply_real(*this,*this,A);
  return *this;
}



qi_class_real & qi_class_real::operator /= (const qi_class_real & A)
{
  multiply_real(*this,*this,inverse(A));
  return *this;
}



void
swap(qi_class_real & A, qi_class_real & B)
{
  qi_class_real C;

  C.assign(A);
  A.assign(B);
  B.assign(C);
}




bool
prime_ideal(qi_class_real & A, const bigint & p)
{
  bigint D,b,temp;
  int kro,Dp,ip;

  D.assign(qi_class_real::discriminant());
  kro = kronecker(D,p);
  if (kro < 0)
    return false;
  else {
    if (p == 2) {
      if (kro == 0) {
        Dp = (int) remainder(D,16);
        if (Dp == 8)
          b.assign_zero();
        else if (Dp == 12)
          b.assign(p);
        else
          return false;
      }
      else
        b.assign(1);
    }
    else {
      if (kro == 0) {
        remainder(temp,D,p*p);
        if (temp.is_zero())
          return false;
        else
          b.assign_zero();
      }
      else {
        if (is_int(p)) {
          p.intify(ip);
          Dp = (int) remainder(D,(long) ip);
          if (Dp < 0)
            Dp += ip;

          b.assign(ressol(Dp,ip));
        }
        else {
          remainder(temp,D,p);
          if (temp.is_lt_zero())
            add(temp,temp,p);
          ressol(b,temp,p);
        }

        if (b.is_lt_zero())
          add(b,b,p);
      }

      if ((D.is_odd()) != (b.is_odd()))
        subtract(b,p,b);
    }

    A.assign(p,b);
    return true;
  }
}



void
qi_class_real::rho()
{
  bigint a2,q,temp;
  bigfloat temp2,temp3;

  /* d += log( (b+sqrt(Delta)) / (2a)) */
  add(temp2,rd,b);
  divide(temp2,temp2,a);
  temp2.divide_by_2();
  add(d,d,log(temp2));

  /* a = (D - b^2) / 4a */
  shift_left(a2,a,2);
  square(temp,b);
  subtract(temp,Delta,temp);
  divide(a,temp,a2);

  /* q = floor((b + rootD) / 2a) */
  shift_left(a2,a,1);
  add(q,rootD,b);
  temp2.assign(q,a2);
  temp3 = floor(temp2);
  temp3.bigintify(q);

  /* b = 2aq - b */
  multiply(temp,a2,q);
  subtract(b,temp,b);
}




void
apply_rho(qi_class_real & A, const qi_class_real & B)
{
  A.assign(B);
  A.rho();
}



qi_class_real
apply_rho(const qi_class_real & A)
{
  qi_class_real B;
 
  B.assign(A);
  B.rho();
  return B;
}




void
qi_class_real::inverse_rho()
{
  bigint a2,q,temp;
  bigfloat temp2,temp3;

  /* q = floor((b + rootD) / 2a) */
  shift_left(a2,a,1);
  add(q,b,rootD);
  temp2.assign(q,a2);
  temp3 = floor(temp2);
  temp3.bigintify(q);

  /* b = 2aq - b */
  multiply(temp,a2,q);
  subtract(b,temp,b);

  /* a = (D - b^2) / 4a */
  a2.multiply_by_2();
  square(temp,b);
  subtract(temp,Delta,temp);
  divide(a,temp,a2);

  /* d -= log( (b+sqrt(Delta)) / (2a)) */
  add(temp2,rd,b);
  divide(temp2,temp2,a);
  temp2.divide_by_2();
  subtract(d,d,log(temp2));
}



void
apply_inverse_rho(qi_class_real & A, const qi_class_real & B)
{
  A.assign(B);
  A.inverse_rho();
}



qi_class_real
apply_inverse_rho(const qi_class_real & A)
{
  qi_class_real B;
 
  B.assign(A);
  B.inverse_rho();
  return B;
}


bool
qi_class_real::is_principal() const
{
  bool is_prin;
  bigfloat dist;
  int num;
  char str[150];

  num = bigint_to_string(Delta,str);
  if (num < PBJTB)
    is_prin = is_principal_buch(dist);
  else
    is_prin = is_principal_subexp(dist);

  return is_prin;
}



bool
qi_class_real::is_principal_buch() const
{
  bigfloat dist;

  return is_principal_buch(dist);
}



bool
qi_class_real::is_principal_subexp() const
{
  bigfloat dist;

  return is_principal_subexp(dist);
}



bool
qi_class_real::is_principal(bigfloat & dist) const
{
  bool is_prin;
  int num;
  char str[150];

  num = bigint_to_string(Delta,str);
  if (num < PBJTB)
    is_prin = is_principal_buch(dist);
  else
    is_prin = is_principal_subexp(dist);

  return is_prin;
}



bool
qi_class_real::is_principal_buch(bigfloat & dist) const
{
  bigfloat y,usqr,u,temp;
  long upper;
  bigint temp2;
  qi_class_real A,C,BB,D,G,U,*SVAL;
  bool is_prin;
  timer t;
  quadratic_order *cur_qo;

  if (!current_order)
    lidia_error_handler("qi_class_real", "is_principal_buch() - no current \
quadratic order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class_real", "order_in_CL() - the current \
quadratic order has been deleted");

  cur_qo = current_order->get_qo();

  t.start_timer();

  G.assign(*this,0.0);
  U.assign_one();
  dist.assign_zero();

  if (this->is_one())
    is_prin = true;
  else {
    is_prin = false;

    if (cur_qo->prin_list.no_of_elements() > 0) {
      A = cur_qo->prin_list.last_entry();
      if ((A.is_one()) && (A.d.is_gt_zero())) {
        /* whole cycle has already been computed - table look-up */
        SVAL = cur_qo->prin_list.search(G);
        if (SVAL) {
          is_prin = true;
          dist = SVAL->get_distance();
        }
        else {
          is_prin = false;
          dist = A.d;
        }
      }
      else {
        SVAL = cur_qo->prin_list.search(G);
        if (SVAL) {
          is_prin = true;
          dist = SVAL->d;
        }
        ceil(temp,A.d);
        temp.bigintify(temp2);
        if (temp2.is_odd())
          inc(temp2);
        u.assign(temp2);
        y.assign(u);
      }
    }
    else {
      /* get hash table size */
      temp = ceil(power(bigfloat(Delta),bigfloat(0.25)));
      temp.multiply_by_2();
      multiply(temp,temp,bigfloat(1.33));
      temp.longify(upper);
      cur_qo->prin_list.initialize((lidia_size_t) upper);
      cur_qo->prin_list.set_key_function(&qi_class_real_key);

      add(temp,log(bigfloat(Delta)),3);
      shift_right(temp,temp,2);
      ceil(temp,temp);
      temp.bigintify(temp2);
      if (temp2.is_odd())
        inc(temp2);
      u.assign(temp2);
      y.assign(u);

      A.assign_one();
      cur_qo->prin_list.hash(A);
    }
  }

  BB.assign_one();
  D.assign(G);

  while ((dist.is_zero()) && (!is_prin)) {
    /* compute more baby steps */
    while (A.d < u) {
      A.rho();
      cur_qo->prin_list.hash(A);
      if (A.is_equal(G)) {
        dist.assign(A.d);
        is_prin = true;
        break;
      }
      if (A.is_one()) {
        dist.assign(A.d);
        is_prin = false;
        break;
      }
    }

    /* compute giant steps to u^2 */
    inverse(C,A);
    while (abs(C.d) <= u)
      C.inverse_rho();
    while (abs(C.d) > u)
      C.rho();

    square(usqr,u);
    while ((y.compare(usqr) < 0) && (dist.is_zero())) {
      multiply_real(D,D,C);
      while (abs(D.d) <= y)
        D.inverse_rho();
      while (abs(D.d) > y)
        D.rho();

      SVAL = cur_qo->prin_list.search(D);
      if (SVAL) {
        /* found D in list:  z = y-r */
        subtract(dist,D.d,SVAL->d);
        dist.absolute_value();
        is_prin = true;
      }
      else {
        multiply_real(BB,BB,C);
        while (abs(BB.d) <= y)
          BB.inverse_rho();
        while (abs(BB.d) > y)
          BB.rho();

        SVAL = cur_qo->prin_list.search(BB);
        if (SVAL) {
          /* found b in list:  z = y-r */
          subtract(dist,BB.d,SVAL->d);
          dist.absolute_value();
          is_prin = false;
        }
        else
          add(y,y,u);
      }
    }

    u.multiply_by_2();
  }

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (is_principal_buch) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return is_prin;
}




/* FIX */
bool
qi_class_real::is_principal_subexp(bigfloat & dist) const
{
  timer t;

  t.start_timer();

  dist.assign_zero();

  warning_handler_c("qi_class_real","is_principal_subexp() - not implemented", return false);

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (is_principal_subexp) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return this->is_one();
}




bool
qi_class_real::is_equivalent(const qi_class_real & B) const
{
  qi_class_real A;
  bool is_equiv;
  bigfloat dist;

  multiply(A,inverse(*this),B);
  A.assign(A,bigfloat(0.0));
  is_equiv = A.is_principal(dist);

  return is_equiv;
}



bool
qi_class_real::is_equivalent_buch(const qi_class_real & B) const
{
  qi_class_real A;
  bool is_equiv;
  bigfloat dist;

  multiply(A,inverse(*this),B);
  A.assign(A,bigfloat(0.0));
  is_equiv = A.is_principal_buch(dist);

  return is_equiv;
}




bool
qi_class_real::is_equivalent_subexp(const qi_class_real & B) const
{
  qi_class_real A;
  bool is_equiv;
  bigfloat dist;

  multiply(A,inverse(*this),B);
  A.assign(A,bigfloat(0.0));
  is_equiv = A.is_principal_subexp(dist);

  return is_equiv;
}


bool
qi_class_real::is_equivalent(const qi_class_real & B, bigfloat & dist) const
{
  bool is_equiv;
  int num;
  char str[150];

  num = bigint_to_string(Delta,str);
  if (num < PBJTB)
    is_equiv = is_equivalent_buch(B,dist);
  else
    is_equiv = is_equivalent_subexp(B,dist);

  return is_equiv;
}



bool
qi_class_real::is_equivalent_buch(const qi_class_real & B, bigfloat & dist) const
{
  bigfloat y,usqr,u,temp;
  bigint temp2;
  long upper;
  qi_class_real A,C,BB,D,G,*SVAL,U;
  bool is_equiv;
  timer t;
  hash_table <qi_class_real> elist;

  t.start_timer();

  G.assign(*this,0.0);
  dist.assign_zero();
  U.assign_one();

  if (this->is_equal(B))
    is_equiv = true;
  else {
    is_equiv = false;

    /* get hash table size */
    temp = ceil(power(bigfloat(Delta),bigfloat(0.25)));
    temp.multiply_by_2();
    multiply(temp,temp,bigfloat(1.33));
    temp.longify(upper);
    elist.initialize((lidia_size_t) upper);
    elist.set_key_function(&qi_class_real_key);

    add(temp,log(bigfloat(Delta)),3);
    shift_right(temp,temp,2);
    ceil(temp,temp);
    temp.bigintify(temp2);
    if (temp2.is_odd())
      inc(temp2);
    u.assign(temp2);
    y.assign(u);

    A.assign(B,bigfloat(0.0));
  }

  BB.assign(B,0.0);
  D.assign(G);

  while ((dist.is_zero()) && (!is_equiv)) {
    /* compute more baby steps */
    while (A.d < u) {
      A.rho();
      elist.hash(A);
      if (A.is_equal(G)) {
        dist.assign(A.d);
        is_equiv = true;
        break;
      }
      if (A.is_equal(B)) {
        dist.assign(A.d);
        is_equiv = false;
        break;
      }
    }

    /* compute giant steps to u^2 */
    C.assign(nearest(U,-u));
    while (abs(C.d) <= u)
      C.inverse_rho();
    while (abs(C.d) > u)
      C.rho();

    square(usqr,u);
    while ((y.compare(usqr) < 0) && (dist.is_zero())) {
      multiply_real(D,D,C);
      while (abs(D.d) <= y)
        D.inverse_rho();
      while (abs(D.d) > y)
        D.rho();

      SVAL = elist.search(D);
      if (SVAL) {
        /* found D in list:  z = y-r */
        subtract(dist,D.d,SVAL->d);
        dist.absolute_value();
        is_equiv = true;
      }
      else {
        multiply_real(BB,BB,C);
        while (abs(BB.d) <= y)
          BB.inverse_rho();
        while (abs(BB.d) > y)
          BB.rho();

        SVAL = elist.search(BB);
        if (SVAL) {
          /* found b in list:  z = y-r */
          subtract(dist,BB.d,SVAL->d);
          dist.absolute_value();
          is_equiv = false;
        }
        else
          add(y,y,u);
      }
    }

    u.multiply_by_2();
  }

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (is_equivalent_buch) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return is_equiv;
}



bool
qi_class_real::is_equivalent_subexp(const qi_class_real & B, bigfloat & dist) const
{
  timer t;

  t.start_timer();

  dist.assign_zero();

  warning_handler_c("qi_class_real","is_equivalent_subexp() - not implemented", return false);

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (is_equivalent_subexp) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return this->is_equal(B);
}



base_vector <bigint>
subgroup(base_vector <qi_class_real> & G, base_vector <qi_class_real> & factor_base, bigint_matrix & U)
{
  lidia_size_t i,l;
  base_vector <qi_class> A,FB;
  base_vector <bigint> S;
  qi_class Amem;

  A.set_mode(EXPAND);
  FB.set_mode(EXPAND);
  factor_base.set_mode(EXPAND);
  S.set_mode(EXPAND);
  A.set_size(0);
  FB.set_size(0);
  factor_base.set_size(0);
  S.set_size(0);

  l = G.size();
  A.set_size(l);
  for (i=0; i<l; ++i)
    A[i] = qi_class(G[i]);

  S = subgroup(A,FB,U);
  l = FB.size();
  for (i=0; i<l; ++i)
    factor_base[i] = (qi_class_real) FB[i];

  return S;
}



base_vector <bigint>
subgroup_BJT(base_vector <qi_class_real> & G, base_vector <qi_class_real> & factor_base, bigint_matrix & U, base_vector <long> & v)
{
  lidia_size_t i,l;
  base_vector <qi_class> A,FB;
  base_vector <bigint> S;
  qi_class Amem;

  A.set_mode(EXPAND);
  FB.set_mode(EXPAND);
  factor_base.set_mode(EXPAND);
  S.set_mode(EXPAND);
  A.set_size(0);
  FB.set_size(0);
  factor_base.set_size(0);
  S.set_size(0);

  l = G.size();
  A.set_size(l);
  for (i=0; i<l; ++i)
    A[i] = qi_class(G[i]);

  S = subgroup_BJT(A,FB,U,v);
  l = FB.size();
  for (i=0; i<l; ++i)
    factor_base[i] = (qi_class_real) FB[i];

  return S;
}


base_vector <bigint>
subgroup_h(base_vector <qi_class_real> & G, base_vector <qi_class_real> & factor_base, bigint_matrix & U)
{
  lidia_size_t i,l;
  base_vector <qi_class> A,FB;
  base_vector <bigint> S;
  qi_class Amem;

  A.set_mode(EXPAND);
  FB.set_mode(EXPAND);
  factor_base.set_mode(EXPAND);
  S.set_mode(EXPAND);
  A.set_size(0);
  FB.set_size(0);
  factor_base.set_size(0);
  S.set_size(0);

  l = G.size();
  A.set_size(l);
  for (i=0; i<l; ++i)
    A[i] = qi_class(G[i]);

  S = subgroup_h(A,FB,U);
  l = FB.size();
  for (i=0; i<l; ++i)
    factor_base[i] = (qi_class_real) FB[i];

  return S;
}



istream & operator >> (istream & in, qi_class_real & A)
{
  int n = 0, sz = 3;
  char c;
  bigint *ibuf;
  bigfloat rbuf;

  ibuf = new bigint[sz];
  in >> c;
  if (c != '(')
    lidia_error_handler("qi_class", "operator>>::( expected");
  in >> c;
  while (c != ')' && n != 3)
    {
      in.putback(c);
      if (n < 2)
        in >> ibuf[n];
      else
        in >> rbuf;
      n++;
      in >> c;
    }

  A.assign(ibuf[0], ibuf[1], rbuf);
  delete[] ibuf;

  return in;
}



ostream & operator << (ostream & out, const qi_class_real & A)
{
  out << "(" << A.a << ", " << A.b << ", " << A.d << ")";
  return out;
}
