#include <cstdlib>
#include <ctime>
#include <limits>
#include <cfloat>
#include <cmath>
#include <iostream>
#include <cassert>

#ifdef _WIN32
#define random() rand()
#define nextafterf(a, b) (throw "no nextafterf", 0);
#endif

#include <util/ieee_float.h>

#define PINF (std::numeric_limits<float>::infinity())
#define NINF (-std::numeric_limits<float>::infinity())
#ifndef NZERO
#define NZERO (-0.0f)
#endif
#define PZERO (0.0f)

#ifndef NAN
#  define NAN (std::numeric_limits<float>::quiet_NaN())
#endif


std::string float2binary(float f)
{
  union
  {
    float f;
    int i;
  } c;

  c.f = f;
  return integer2binary(c.i, 32);
}

float random_float()
{
  union
  {
    float f;
    unsigned i;
  } u;


  unsigned r = ((unsigned) random()) % 20;

  switch(r)
  {
    case 0:
      return PINF;
      break;
    case 1:
      return NINF;
      break;
    case 2:
      return NAN;
      break;
    case 3:
      return PZERO;
      break;
    case 4:
      return NZERO;
      break;
    default:
      u.i=random();
      u.i=(u.i<<16)^random();
      return u.f;
  }

}

bool eq(const ieee_floatt &a, const ieee_floatt &b)
{
  if(a.is_NaN() && b.is_NaN()) return true;
  if(a.is_infinity() && b.is_infinity() && a.get_sign()==b.get_sign()) return true;
  return a==b;
}

typedef enum { PLUS=0, MINUS=1, MULT=2, DIV=3 } binopt;
typedef enum { EQ=0, NEQ=1, LT=2, LE=3, GT=4, GE=5} binrel;
const char *binopsyms[]={ " + ", " - ", " * ", " / " };
const char *binrelsyms[]={ " == ", " != ", " < ", " <= ", " > ", " >= "};

void check_arithmetic(int i)
{
  ieee_floatt i1, i2, i3, res;
  float f1, f2, f3;

  f1=random_float();
  f2=random_float();
  i1.from_float(f1);
  i2.from_float(f2);
  res=i1;
  f3=f1;

  int op=(binopt)i%4;

  switch(op)
  {
    case PLUS:
      f3+=f2;
      res+=i2;
      break;

    case MINUS:
      f3-=f2;
      res-=i2;
      break;

    case MULT:
      f3*=f2;
      res*=i2;
      break;

    case DIV:
      f3/=f2;
      res/=i2;
      break;

    default:assert(0);
  }

  i3.from_float(f3);

  if(!eq(res, i3))
  {
    const char *opsym=binopsyms[op];
    std::cout << i1 << opsym << i2 << " != " << res << std::endl;
    std::cout << f1 << opsym << f2 << " == " << f3 << std::endl;
    std::cout << integer2binary(i1.get_fraction(), i1.spec.f+1) << opsym <<
      integer2binary(i2.get_fraction(), i1.spec.f+1) << " != " <<
      integer2binary(res.get_fraction(), i1.spec.f+1) <<
      " (" << res.get_fraction() << ")" << std::endl;
    std::cout << integer2binary(i1.get_fraction(), i1.spec.f+1) << opsym <<
      integer2binary(i2.get_fraction(), i1.spec.f+1) << " == " <<
      integer2binary(i3.get_fraction(), i1.spec.f+1) <<
      " (" << i3.get_fraction() << ")" << std::endl;
    std::cout << std::endl;
  }
}

void check_comparison(int i)
{
  ieee_floatt i1, i2;
  float f1, f2;

  bool ires, fres;

  f1=random_float();
  f2=random_float();
  i1.from_float(f1);
  i2.from_float(f2);

  int op=(binrel)i%6;

  switch(op)
  {
    case EQ:
      ires = ieee_equal(i1, i2);
      fres = (f1 == f2);
      break;
    case NEQ:
      ires = ieee_not_equal(i1, i2);
      fres = (f1 != f2);
      break;
    case LT:
      ires = (i1 < i2);
      fres = (f1 < f2);
      break;
    case LE:
      ires = (i1 <= i2);
      fres = (f1 <= f2);
      break;
    case GT:
      ires = (i1 > i2);
      fres = (f1 > f2);
      break;
    case GE:
      ires = (i1 >= i2);
      fres = (f1 >= f2);
      break;
    default:
      assert(0);
  }

  if(ires != fres)
  {
    const char *opsym=binrelsyms[op];
    std::cout << i1 << opsym << i2 << " != " << ires << std::endl;
    std::cout << f1 << opsym << f2 << " == " << fres << std::endl;
    std::cout << integer2binary(i1.get_fraction(), i1.spec.f+1) << opsym <<
      integer2binary(i2.get_fraction(), i1.spec.f+1) << " != " << ires;
    std::cout << integer2binary(i1.get_fraction(), i1.spec.f+1) << opsym <<
      integer2binary(i2.get_fraction(), i1.spec.f+1) << " == " << fres;
    std::cout << std::endl;
  }

}

void check_conversion(int i)
{
  union
  {
    float f;
    unsigned i;
  } a, b;

  a.f = random_float();

  ieee_floatt t;
  t.from_float(a.f);

  assert(t.is_float());
  b.f = t.to_float();

  if(a.i != b.i && !((a.f != a.f) && (b.f != b.f)))
  {
    std::cout << "conversion failure: " << a.f << " -> " << t << " -> "
              << b.f << std::endl;
  }
}

void check_nextafter(int i)
{
  float f1 = random_float();
  float f2 = nextafterf(f1, PINF);
  float f3 = nextafterf(f1, NINF);

  ieee_floatt i1, i2, i3;

  i1.from_float(f1);
  i2 = i1;
  i2.increment(false);
  i3 = i1;
  i3.decrement(false);

  if((f1 != i1.to_float() && !(f1 != f1 && i1.is_NaN())) ||
     (f2 != i2.to_float() && !(f2 != f2 && i2.is_NaN())) ||
     (f3 != i3.to_float() && !(f3 != f3 && i3.is_NaN())))
  {
    std::cout << "Incorrect nextafter: " << std::endl
              << "mach float: " << f1 << " " << f2 << " " << f3 << std::endl
              << "ieee_float: " << i1.to_float() << " "
                  << i2.to_float() << " " << i3.to_float() << std::endl;
    std::cout << "Binary representation: " << std::endl
              << "mach float: " << float2binary(f1) << " "
              << float2binary(f2) << " " << float2binary(f3) << std::endl
              << "ieee_float: " << float2binary(i1.to_float()) << " "
                  << float2binary(i2.to_float())
                  << " " << float2binary(i3.to_float()) << std::endl;

  }
}

void check_minmax()
{
  float f = 0;
  ieee_floatt t;
  t.from_float(f);

  t.make_fltmax();
  if(t.to_float() != FLT_MAX)
    std::cout << "make_fltmax is broken" << std::endl;

  t.make_fltmin();
  if(t.to_float() != FLT_MIN)
    std::cout << "make_fltmin is broken:"
             << std::endl << " machine float: " << FLT_MIN
            << ", ieee_floatt: " << t.to_float() << "("
            << (t.to_float() == FLT_MIN) <<  ")" << std::endl;
}

void check_build_extract()
{
  float f = random_float();
  ieee_floatt t;
  t.from_float(f);

  mp_integer old_frac, old_exp;
  t.extract_base2(old_frac, old_exp);
  mp_integer frac_bak=old_frac, exp_bak=old_exp;
  t.build(old_frac, old_exp);
  t.extract_base2(old_frac, old_exp);
  if(frac_bak != old_frac || exp_bak != old_exp)
    std::cout << "extract - build - extract is broken for " << t << std::endl;
}

int main()
{
  srand(time(0));
  check_minmax();

  for(unsigned i=0; i<100000; i++)
  {
    if(i%10000==0) std::cout << "*********** " << i << std::endl;
    check_arithmetic(i);
    check_comparison(i);
    check_conversion(i);
    check_nextafter(i);
    check_build_extract();
  }
}
