
int my_max(int a, int b) {
  if (a > b)
    return a;
  else
    return b;
}

// biginteger routines

int make_biginteger() {
  int start;
  start = bigfloat_pool_idx;
  bigfloat_pool_idx = bigfloat_pool_idx + MAX_DIGITS + 1;
  if (bigfloat_pool_idx >= BIGFLOAT_POOL_SIZE) {
    printf("out of bigfloat memory");
    writeln();
    my_exit(0);
  }
  return start;
}

void biginteger_copy(int src, int dest) {
//for (i = 0; i < MAX_DIGITS; i++)
//  Xset_digit(dest, i, Xget_digit(src, i));
  memcpy32(bigfloat_pool, dest, bigfloat_pool, src, MAX_DIGITS);
  set_size(dest, get_size(src));
  return;
}

void initialize_biginteger(int n) {
//for (i = 0; i < MAX_DIGITS; i++)
//  Xset_digit(n, i, 0);
  memset32(bigfloat_pool, n, 0, MAX_DIGITS);
  set_size(n, 0);
  return;
}

void int_to_biginteger(int s, int n) {
  int digit;

  // s assumed to be positive

  initialize_biginteger(n);

  set_size(n, -1);

  while (s > 0) {
    set_size(n, get_size(n) + 1);
    digit = s % DIGIT_BASE;
    set_digit(n, get_size(n), digit);
    s = s / DIGIT_BASE;
  }

  return;
}

int biginteger_to_int(int n, int n_digits) {
  int size;
  int power;
  int sum;
  int i;
  size = get_size(n) + 1;
  power = 1;
  sum = 0;
  for (i = size - n_digits; i < size; i++) {
    sum = sum + power * get_digit(n, i);
    power = power * DIGIT_BASE;
  }
  return sum;
}

void zero_justify(int n) {
  while ((get_size(n) > 0) && (get_digit(n, get_size(n)) == 0))
    set_size(n, get_size(n) - 1);
  return;
}

void add_biginteger(int a, int b, int c) {
  int size;
  int carry;
  int size_a;
  int size_b;
  int digit_sum;
  int i;

  initialize_biginteger(c);

  size_a = get_size(a);
  size_b = get_size(b);
  size = my_max(size_a, size_b) + 1;
  set_size(c, size);

  carry = 0;

  for (i = 0; i <= size; i++) {
    digit_sum = carry + get_digit(a, i) + get_digit(b, i);
    if (digit_sum >= DIGIT_BASE) {
      digit_sum = digit_sum - DIGIT_BASE;
      carry = 1;
    }
    else
      carry = 0;
    set_digit(c, i, digit_sum);
  }

  zero_justify(c);

  return;
}

void subtract_biginteger(int a, int b, int c) {
  int borrow;
  int val;
  int i;
  int size_a;
  int size_b;
  int size_c;
  int a_digit;

  // only works if a >= b

  initialize_biginteger(c);

  size_a = get_size(a);
  size_b = get_size(b);
  size_c = my_max(size_a, size_b);
  set_size(c, size_c);

  borrow = 0;

  for (i = 0; i <= size_c; i++) {
    a_digit = get_digit(a, i);
    val = a_digit - borrow - get_digit(b, i);
    if (a_digit > 0)
      borrow = 0;
    if (val < 0) {
      val = val + DIGIT_BASE;
      borrow = 1;
    }

    if (val >= DIGIT_BASE)
      val = val - DIGIT_BASE;
    set_digit(c, i, val);
  }

  zero_justify(c);

  return;
}

int compare_biginteger(int a, int b) {
  int size_a;
  int size_b;

  size_a = get_size(a);
  size_b = get_size(b);

  if (size_b > size_a)
    return 1;
  else
  if (size_a > size_b)
    return -1;
  else
  if (size_a == -1) // size is 0
    return 0;
  else
    return memcmp32(bigfloat_pool, a, b, size_a);
}

void shift_right(int n, int d) { // multiply n by DIGIT_BASE^d
  int i;
  int size_n;

  size_n = get_size(n);

  if ((size_n == 0) && (get_digit(n, 0) == 0))
    return;
  else {

    for (i = size_n; i >= 0; i--)
      set_digit(n, i + d, get_digit(n, i));

    if (d == 1)
      set_digit_zero(n, 0);
    else
//for (i = 0; i < d; i++)
//  Xset_digit(n, i, 0);
      memset32(bigfloat_pool, n, 0, d);

    set_size(n, size_n + d);

    return;
  }
}

void multiply_one_digit(int a, int b, int n) {
  int i;
  int c;
  int size;
  int digit;

  size = get_size(a);
  c = 0;
  for (i = 0; i <= size; i++) {
    set_digit(b, i, n * get_digit(a, i));
    set_digit(b, i, c + get_digit(b, i));
    digit = get_digit(b, i);
    if (digit >= DIGIT_BASE) { // overflow
      c = digit / DIGIT_BASE;
      set_digit(b, i, digit % DIGIT_BASE);
    }
    else
      c = 0;
  }

  if (c == 0)
    set_size(b, size);
  else {
    set_digit(b, size + 1, c);
    set_size(b, size + 1);
  }
  return;
}

void multiply_biginteger(int a, int b, int c) {
  int i;

  initialize_biginteger(c);

  for (i = 0; i <= get_size(a); i++) {
    initialize_biginteger(row);
    multiply_one_digit(b, row, get_digit(a, i));
    if (i != 0)
      shift_right(row, i);
    add_biginteger(c, row, tmp);
    biginteger_copy(tmp, c);
  }

  return;
}

void divide_biginteger(int a, int b, int c) {
  int i;
  int size_a;
  int digit;

  initialize_biginteger(c);
  int_to_biginteger(0, row);
  initialize_biginteger(tmp);

  size_a = get_size(a);

  set_size(c, size_a);

  for (i = size_a; i >= 0; i--) {
    shift_right(row, 1);
    set_digit(row, 0, get_digit(a, i));
    digit = 0;
    while (compare_biginteger(row, b) <= 0) { // negative or 0
      digit = digit + 1;
      subtract_biginteger(row, b, tmp);
      biginteger_copy(tmp, row);
    }
    set_digit(c, i, digit);
  }

  zero_justify(c);

  return;
}

int is_biginteger_zero(int n) { // TODO should be able to just test size = -1
  int processing;
  int ret;
  int i;
  i = 0;
  processing = 1;
  ret = 1; // by default assume zero
  while (processing) {
    if (i < MAX_DIGITS)
      if (get_digit(n, i) != 0) {
	processing = 0;
        ret = 0; // non zero
      }
      else
        i = i + 1;
    else
      processing = 0;
  }
  return ret;
}

int digits_in_integer(int n) {
  int count;
  if (n == 0)
    return 0;
  else {
    n = abs(n);
    count = 0;
    while (n > 0) {
      n = n / DIGIT_BASE;
      count = count + 1;
    }
    return count;
  }
}

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();
    my_exit(0);
  }
  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;
}

// 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));
}
