// bigfloat routines

int make_bigfloat() {
  int start;
  start = bigfloat_pool_idx;
  bigfloat_pool_idx = bigfloat_pool_idx + MAX_DIGITS + 3;
  if (bigfloat_pool_idx >= BIGFLOAT_POOL_SIZE) {
    printf("out of bigfloat memory");
    writeln();
    return 0; // TODO may not be that graceful
  }
  return start;
}

void init_bigfloat(int x, int n, int exponent) {
  int mantissa;
  if (n >= 0) // TODO for now n is assumed to be positive
    set_sign(x, 0);
  else
    set_sign(x, 1);
  set_exponent(x, exponent);
  mantissa = get_mantissa(x);
  initialize_biginteger(mantissa);
  int_to_biginteger(n, mantissa);
  if (n != 0)
    shift_right(mantissa, (PRECISION - get_size(mantissa)) - 1);
  return;
}

void init_bigfloat_from_integer(int x, int n) {
  int mantissa;
  if (n >= 0)
    set_sign(x, 0);
  else
    set_sign(x, 1);
  set_exponent(x, digits_in_integer(abs(n)));
  mantissa = get_mantissa(x);
  initialize_biginteger(mantissa);
  int_to_biginteger(abs(n), mantissa);
  if (n != 0)
    shift_right(mantissa, (PRECISION - get_size(mantissa)) - 1);
  return;
}

int make_and_init_bigfloat(int n, int exponent) {
  int x;
  x = make_bigfloat();
  init_bigfloat(x, n, exponent);
  return x;
}

void shift_left(int n, int drop_n) {
  // shift mantissa left by drop_n least significant digits
  int i;
  int size;
  int new_size;
  size = get_size(n) + 1;
  new_size = size - drop_n;
  for (i = 0; i < new_size; i++)
    set_digit(n, i, get_digit(n, i + drop_n));
  for (i = size - drop_n; i < size; i++)
    set_digit(n, i, 0);
  set_size(n, size - drop_n - 1);
  return;
}

int is_bigfloat_zero(int n) {
  return is_biginteger_zero(get_mantissa(n)); // ignores sign and exponent
}

int is_bigfloat_equal(int x, int y) {
  int xs;
  int xm;
  int xe;
  int ys;
  int ym;
  int ye;

  if (is_bigfloat_zero(x) && is_bigfloat_zero(y))
    // zero mantissas can have different lengths
    return 1;
  else {
    xs = get_sign(x);
    xm = get_mantissa(x);
    xe = get_exponent(x);
    ys = get_sign(y);
    ym = get_mantissa(y);
    ye = get_exponent(y);
    return (xs == ys) && (xe == ye) && (compare_biginteger(xm, ym) == 0);
  }
}

void copy_bigfloat(int src, int dest) {
  set_sign(dest, get_sign(src));
  set_exponent(dest, get_exponent(src));
  biginteger_copy(get_mantissa(src), get_mantissa(dest));
  return;
}

void negate_bigfloat_destructive(int x) {
  int sign;
  sign = get_sign(x);
  if (sign == 0)
    set_sign(x, 1);
  else
    set_sign(x, 0);
  return;
}

void negate_bigfloat(int x, int y) {
  copy_bigfloat(x, y);
  if (get_sign(x) == 0)
    set_sign(y, 1);
  else
    set_sign(y, 0);
  return;
}

int is_negative_bigfloat(int x) {
  return get_sign(x) == 1;
}

int is_positive_bigfloat(int x) {
  if (is_bigfloat_zero(x))
    return 0; // zero not positive
  else
    return get_sign(x) == 0;
}

int sign_product(int x, int y) { // exclusive or the operands
  if (x)
    if (y)
      return 0;
    else
      return 1;
  else
    if (y)
      return 1;
    else
      return 0;
}

void adjust_biginteger_to_precision(int x) {
  int size;
  size = get_size(x) + 1;
  if (size < PRECISION)
    shift_right(x, PRECISION - size);
  return;
}

void pack_mantissa(int source, int dest, int n) {
  int source_end;
  int value;
  int shift_n;
  int shift_count;
  source_end = source + n;
  shift_n = 0;
  shift_count = 0;
  value = 0;
  while (source < source_end) {
    if (shift_count == 8) {
      cells[dest] = value;
      dest = dest + 1;
      shift_n = 0;
      shift_count = 0;
      value = 0;
    }
    value = value | (bigfloat_pool[source] << shift_n);
    shift_n = shift_n + 4;
    shift_count = shift_count + 1;
    source = source + 1;
  }
  cells[dest] = value;
  return;
}

void unpack_mantissa(int source, int dest, int n) {
  int dest_end;
  int dest_idx;
  int value;
  int shift_count;
  dest_end = dest + n;
  dest_idx = dest;
  shift_count = 0;
  while (dest_idx < dest_end) {
    if (shift_count == 0) {
      value = cells[source];
      source = source + 1;
    }
    bigfloat_pool[dest_idx] = value & 0xf;
    value = value >> 4;
    dest_idx = dest_idx + 1;
    shift_count = shift_count + 1;
    if (shift_count == 8)
      shift_count = 0;
  }
  return;
}

// the four bigfloat arithmentic operations with positive inputs

void plusX(int x, int y, int z) {
  int xm;
  int xe;
  int ym;
  int ye;
  int zm;
  int exponent;
//xs = Xget_sign(x);
  xm = get_mantissa(x);
  xe = get_exponent(x);
//ys = Xget_sign(y);
  ym = get_mantissa(y);
  ye = get_exponent(y);
  zm = get_mantissa(z);
  if (xe > ye) {
    exponent = xe - ye;
    if (exponent >= PRECISION)
      copy_bigfloat(x, z);
    else {
      biginteger_copy(ym, tmp);
      shift_left(tmp, exponent);
      add_biginteger(xm, tmp, zm);
      if (get_size(zm) + 1 > PRECISION) {
        shift_left(zm, 1); // drop least significant digit
        xe = xe + 1;
      } 
      set_sign(z, 0);
      set_exponent(z, xe);
      adjust_biginteger_to_precision(zm);
    }
  }
  else
  if (ye > xe) {
    exponent = ye - xe;
    if (exponent >= PRECISION)
      copy_bigfloat(y, z);
    else {
      biginteger_copy(xm, tmp);
      shift_left(tmp, exponent);
      add_biginteger(ym, tmp, zm);
      if (get_size(zm) + 1 > PRECISION) {
        shift_left(zm, 1); // drop least significant digit
        ye = ye + 1;
      } 
      set_sign(z, 0);
      set_exponent(z, ye);
      adjust_biginteger_to_precision(zm);
    }
  }
  else { // exponents are equal
    add_biginteger(xm, ym, zm);
    if (get_size(zm) + 1 > PRECISION) {
      shift_left(zm, 1); // drop least significant digit
      xe = xe + 1;
    } 
    set_sign(z, 0);
    set_exponent(z, xe);
    adjust_biginteger_to_precision(zm);
  }
  return;
}

void minusX(int x, int y, int z) {
  int xm;
  int xe;
  int ym;
  int ye;
  int zm;
  int exponent;
//xs = Xget_sign(x);
  xm = get_mantissa(x);
  xe = get_exponent(x);
//ys = Xget_sign(y);
  ym = get_mantissa(y);
  ye = get_exponent(y);
  zm = get_mantissa(z);
  if (xe > ye) {
    exponent = xe - ye;
    if (exponent >= PRECISION)
      copy_bigfloat(x, z);
    else {
      biginteger_copy(ym, tmp);
      shift_left(tmp, exponent);
      subtract_biginteger(xm, tmp, zm);
      set_exponent(z, xe - (PRECISION - (get_size(zm) + 1)));
      adjust_biginteger_to_precision(zm);
      set_sign(z, 0);
    }
  }
  else
  if (ye > xe) {
    exponent = ye - xe;
    if (exponent >= PRECISION) { // TODO I dont think this case happens
      copy_bigfloat(y, z);
      set_sign(z, 1);
    }
    else {
      biginteger_copy(xm, tmp);
      shift_left(tmp, exponent);
      subtract_biginteger(ym, tmp, zm);
      set_exponent(z, ye - (PRECISION - (get_size(zm) + 1)));
      adjust_biginteger_to_precision(zm);
      set_sign(z, 1);
    }
  }
  else { // exponents are equal
    if (compare_biginteger(xm, ym) < 0) { // xm > ym
      subtract_biginteger(xm, ym, zm);
      set_exponent(z, xe - (PRECISION - (get_size(zm) + 1)));
      adjust_biginteger_to_precision(zm);
      set_sign(z, 0);
    }
    else
    if (compare_biginteger(ym, xm) < 0) { // ym > xm
      subtract_biginteger(ym, xm, zm);
      set_exponent(z, ye - (PRECISION - (get_size(zm) + 1)));
      adjust_biginteger_to_precision(zm);
      set_sign(z, 1);
    }
    else // 0
      init_bigfloat(z, 0, 0); // set to zero
  }
  return;
}

void multX(int x, int y, int z) {
  int xm;
  int xe;
  int ym;
  int ye;
  int zm;
  int len_;
  int lenx;
  int leny;
  int exponent;
  if (is_bigfloat_zero(x))
    copy_bigfloat(ZERO, z);
  else
  if (is_bigfloat_zero(y))
    copy_bigfloat(ZERO, z);
  else
  if (is_bigfloat_equal(x, ONE))
    copy_bigfloat(y, z);
  else
  if (is_bigfloat_equal(y, ONE))
    copy_bigfloat(x, z);
  else {
//  xs = Xget_sign(x);
    xm = get_mantissa(x);
    xe = get_exponent(x);
//  ys = Xget_sign(y);
    ym = get_mantissa(y);
    ye = get_exponent(y);
    zm = get_mantissa(z);
    multiply_biginteger(xm, ym, zm);
    exponent = xe + ye;
    len_ = get_size(zm) + 1;
    lenx = get_size(xm) + 1;
    leny = get_size(ym) + 1;
    exponent = exponent + len_ - (lenx + leny);
    set_sign(z, 0);
    set_exponent(z, exponent);
    if (len_ > PRECISION)
      shift_left(zm, len_ - PRECISION);
  }
  return;
}

void divX(int x, int y, int z) {
  int xm;
  int xe;
  int ym;
  int ye;
  int zm;
  int exponent;
//int shiftx;
//int shifty;
  if (is_bigfloat_zero(x)) {
    copy_bigfloat(ZERO, z);
    return;
  }
  else {
//  xs = Xget_sign(x);
    xm = get_mantissa(x);
    xe = get_exponent(x);
//  ys = Xget_sign(y);
    ym = get_mantissa(y);
    ye = get_exponent(y);
    zm = get_mantissa(z);
//  shiftx = PRECISION - (get_size(xm) + 1);
//  shifty = PRECISION - (get_size(ym) + 1);
    biginteger_copy(ym, tmp3);
adjust_biginteger_to_precision(tmp3);
    biginteger_copy(xm, tmp4);
adjust_biginteger_to_precision(tmp4);
    shift_right(tmp4, PRECISION); // double size
    divide_biginteger(tmp4, tmp3, zm);
    exponent = xe - ye;
    set_sign(z, 0); // positive
    if (get_size(zm) + 1 > PRECISION) { // if so it will be one digit
      set_exponent(z, exponent + 1);
    }
    else
      set_exponent(z, exponent);
adjust_biginteger_to_precision(zm);
    return;
  }
}

// the four signed bigfloat arithmetic operations

void add_bigfloat(int x, int y, int z) {
  if (is_bigfloat_zero(y))
    copy_bigfloat(x, z);
  else
  if (is_bigfloat_zero(x))
    copy_bigfloat(y, z);
  else
  if (is_positive_bigfloat(x) && is_positive_bigfloat(y))
    plusX(x, y, z);
  else
  if (is_negative_bigfloat(x) && is_positive_bigfloat(y)) {
    negate_bigfloat(x, bigfloat_temp);
    minusX(y, bigfloat_temp, z);
  }
  else
  if (is_positive_bigfloat(x) && is_negative_bigfloat(y)) {
    negate_bigfloat(y, bigfloat_temp);
    minusX(x, bigfloat_temp, z);
  }
  else {
    negate_bigfloat(x, bigfloat_temp);
    negate_bigfloat(y, bigfloat_temp2);
    plusX(bigfloat_temp, bigfloat_temp2, z);
    negate_bigfloat_destructive(z);
  }
  return;
}

void subtract_bigfloat(int x, int y, int z) {
  if (is_bigfloat_zero(y))
    copy_bigfloat(x, z);
  else
  if (is_bigfloat_zero(x)) {
    copy_bigfloat(y, z);
    negate_bigfloat_destructive(z);
  }
  else
  if (is_positive_bigfloat(x) && is_positive_bigfloat(y))
    minusX(x, y, z);
  else
  if (is_negative_bigfloat(x) && is_positive_bigfloat(y)) {
    negate_bigfloat(x, bigfloat_temp);
    plusX(bigfloat_temp, y, z);
    negate_bigfloat_destructive(z);
  }
  else
  if (is_positive_bigfloat(x) && is_negative_bigfloat(y)) {
    negate_bigfloat(y, bigfloat_temp);
    plusX(x, bigfloat_temp, z);
  }
  else {
    negate_bigfloat(x, bigfloat_temp);
    negate_bigfloat(y, bigfloat_temp2);
    minusX(bigfloat_temp2, bigfloat_temp, z);
  }
  return;
}

void multiply_bigfloat(int x, int y, int z) {
  int zm;
  int len_;
  multX(x, y, z);
  set_sign(z, sign_product(get_sign(x), get_sign(y)));

  // TODO this logic is a result of a bug in multX
  zm = get_mantissa(z);
  len_ = get_size(zm) + 1;
  if (len_ > PRECISION)
    shift_left(zm, len_ - PRECISION);
  return;
}

void divide_bigfloat(int x, int y, int z) {
  int zm;
  int len_;
  if (is_bigfloat_zero(y)) { // divide by zero
    copy_bigfloat(INFINITY, z); // TODO should be a new category NaN
    if (is_negative_bigfloat(x))
      set_sign(z, 1); // negative infinity
    return;
  }
  else {
    divX(x, y, z);
    set_sign(z, sign_product(get_sign(x), get_sign(y)));

    // TODO this logic is a result of a bug in divX
    zm = get_mantissa(z);
    len_ = get_size(zm) + 1;
    if (len_ > PRECISION)
      shift_left(zm, len_ - PRECISION);
    return;
  }
}

// bigfloat comparison operations

int less_than_bigfloat(int x, int y) {
  subtract_bigfloat(x, y, bigfloat_temp);
  return (is_negative_bigfloat(bigfloat_temp));
}

int less_than_or_equal_bigfloat(int x, int y) {
  subtract_bigfloat(x, y, bigfloat_temp);
  return (is_negative_bigfloat(bigfloat_temp) || 
          is_bigfloat_zero(bigfloat_temp));
}

int greater_than_bigfloat(int x, int y) {
  subtract_bigfloat(x, y, bigfloat_temp);
  return (is_positive_bigfloat(bigfloat_temp));
}

int greater_than_or_equal_bigfloat(int x, int y) {
  subtract_bigfloat(x, y, bigfloat_temp);
  return (is_positive_bigfloat(bigfloat_temp) || 
          is_bigfloat_zero(bigfloat_temp));
}

// misc bigfloat functions

void truncate_bigfloat(int x, int y) {
  int xe;
  xe = get_exponent(x);
  if (xe < 0)
    init_bigfloat(y, 0, 0); // zero
  else
  if (xe >= PRECISION)
    copy_bigfloat(x, y);
  else {
    copy_bigfloat(x, y);
    shift_left(get_mantissa(y), PRECISION - xe);
    shift_right(get_mantissa(y), PRECISION - xe);
  } 
  return;
}

void floor_bigfloat(int x, int y) {
  if (greater_than_or_equal_bigfloat(x, ZERO))
    truncate_bigfloat(x, y);
  else {
    truncate_bigfloat(x, y);
    if (!(is_bigfloat_equal(x, y))) {
      subtract_bigfloat(y, ONE, floor_temp);
      copy_bigfloat(floor_temp, y);
    }
    // else return y as is
  }
  return;
}

void ceiling_bigfloat(int x, int y) {
  copy_bigfloat(x, ceiling_temp);
  negate_bigfloat_destructive(ceiling_temp);
  floor_bigfloat(ceiling_temp, y);
  negate_bigfloat_destructive(y);
  return;
}

void round_bigfloat(int x, int y) {
  add_bigfloat(x, ONE_HALF, round_temp);
  floor_bigfloat(round_temp, y);
  return;
}

int bigfloat_to_int() {
  truncate_bigfloat(bigfloat_temp4, bigfloat_temp5);
  return biginteger_to_int(get_mantissa(bigfloat_temp5),
                       get_exponent(bigfloat_temp5));
}
