/*
   Copyright (C) 2004 - 2016 by Guillaume Melquiond <guillaume.melquiond@inria.fr>
   Part of the Gappa tool https://gappa.gitlabpages.inria.fr/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the CeCILL Free Software License Agreement or
   under the terms of the GNU General Public License version.

   See the COPYING and COPYING.GPL files for more details.
*/

#include <algorithm>
#include <cassert>
#include <climits>
#include <map>
#include <sstream>

#include "utils.hpp"
#include "numbers/interval_arith.hpp"
#include "numbers/interval_utility.hpp"
#include "numbers/real.hpp"
#include "numbers/round.hpp"
#include "parser/ast.hpp"
#include "parser/pattern.hpp"
#include "proofs/schemes.hpp"

extern bool parameter_constrained;

struct float_format: gs_rounding {
  int min_exp, prec;
  virtual int shift_val(int exp, int sz) const
  { if (min_exp == INT_MIN) return sz - prec;
    return std::max(min_exp - exp, sz - prec); }
  float_format() {}
  float_format(int e, int p): min_exp(e), prec(p) {}
};

typedef std::map< ast_ident const *, float_format > float_formats;
static float_formats formats;

#define REGISTER_FORMAT(name, e, p) \
  formats.insert(std::make_pair(ast_ident::find(#name), float_format(e, p)))

RUN_ONCE(register_formats) {
  REGISTER_FORMAT(ieee_32 ,   -149,  24);
  REGISTER_FORMAT(ieee_64 ,  -1074,  53);
  REGISTER_FORMAT(ieee_128, -16494, 113);
  REGISTER_FORMAT(x86_80  , -16445,  64);
}

static bool use_abs(direction_type type) {
  switch (type) {
  case ROUND_OD:
  case ROUND_NE:
  case ROUND_NZ:
  case ROUND_NO:
  case ROUND_NA: return true;
  case ROUND_NU:
  case ROUND_ND:
  case ROUND_AW:
  case ROUND_ZR:
  case ROUND_UP:
  case ROUND_DN: return false;
  case ROUND_ARGL: assert(false);
  }
  return false;
}

struct float_rounding_class: function_class
{
  float_format format;
  direction_type type;
  std::string rname, ident;
  float_rounding_class(float_format const &f, direction_type t,
                       std::string const &r, std::string const &i)
    : format(f), type(t), rname(r), ident(i) {}
  virtual std::string description() const { return "rounding_" + rname + ident; }
  virtual std::string pretty_name() const;
};

struct float_rounding_generator: function_generator
{
  float_rounding_generator(): function_generator("float") {}
  virtual function_class const *operator()(function_params const &) const;
};

typedef std::map< long long int, float_rounding_class > float_cache;
static float_cache cache;

function_class const *float_rounding_generator::operator()(function_params const &p) const {
  if (p.empty()) return NULL;
  ast_ident const *nf = param_ident(p[0]);
  int nd;
  float_format f;
  std::string name = "float";
  if (nf) {
    if (p.size() != 2) return NULL;
    float_formats::const_iterator i = formats.find(nf);
    if (i == formats.end()) return NULL;
    f = i->second;
    nd = 1;
  } else {
    if (p.size() < 2 || p.size() > 3) return NULL;
    if (!param_int(p[0], f.prec)) return NULL;
    if (p.size() == 3) {
      if (!param_int(p[1], f.min_exp)) return NULL;
      nd = 2;
    } else {
      f.min_exp = INT_MIN;
      nd = 1;
      name = "floatx";
    }
  }
  direction_type d = get_direction(p[nd]);
  if (d == ROUND_ARGL) return NULL;
  long long int h = (((long long int)f.min_exp) << 24) | (f.prec << 8) | (int)d;
  float_cache::const_iterator j = cache.find(h);
  if (j != cache.end()) return &j->second;
  std::ostringstream s;
  s << ',' << directions[d].name << ',' << f.prec;
  if (f.min_exp != INT_MIN) s << ',' << f.min_exp;
  j = cache.insert(std::make_pair(h, float_rounding_class(f, d, name, s.str()))).first;
  return &j->second;
}

static float_rounding_generator dummy;

static int exponent(number const &n, float_format const &f) {
  mpz_t m;
  int e;
  int s;
  mpz_init(m);
  split_exact(n.data->val, m, e, s);
  if (s == 0) e = f.min_exp;
  else {
    e -= f.prec - mpz_sizeinbase(m, 2);
    if (e < f.min_exp) e = f.min_exp;
  }
  mpz_clear(m);
  return e;
}

static int exponent(number const &n) {
  mpz_t m;
  int e;
  int s;
  mpz_init(m);
  split_exact(n.data->val, m, e, s);
  if (s == 0) e = INT_MIN;
  else e += mpz_sizeinbase(m, 2);
  mpz_clear(m);
  return e;
}

static bool near_pow2(number const &n, int e, int e_infl, bool widen) {
  mpfr_t x;
  mpfr_init2(x, 150);
  mpfr_set_ui_2exp(x, 1, e, GMP_RNDN);
  if (widen) {
    mpfr_t y;
    mpfr_init(y);
    mpfr_set_ui_2exp(y, 1, e_infl, GMP_RNDN);
    mpfr_add(x, x, y, GMP_RNDD);
    mpfr_clear(y);
  }
  int cmp = mpfr_cmpabs(n.data->val, x);
  mpfr_clear(x);
  return cmp <= 0;
}

std::string float_rounding_class::pretty_name() const
{
  std::ostringstream s;
  s << "float<" << format.prec << ',';
  if (format.min_exp != INT_MIN) s << format.min_exp << ',';
  s << directions[type].name << '>';
  return s.str();
}

// BND(float(a)) <- BND(a)
REGISTER_SCHEME_BEGIN(bnd_float);
  float_rounding_class const *rnd;
  bnd_float_scheme(ast_real const *r, ast_real const *rr, char const *n, float_rounding_class const *f)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, predicated_real(rr, PRED_BND)), n), rnd(f) {}
REGISTER_SCHEME_END(bnd_float);

void bnd_float_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval const &i = hyps[0].bnd();
  rounding_fun f = directions[rnd->type].function;
  number a = round_number(lower(i), &rnd->format, f);
  number b = round_number(upper(i), &rnd->format, f);
  res.bnd() = interval(a, b);
}

proof_scheme *bnd_float_scheme::factory(ast_real const *real)
{
  real_op const *p = boost::get<real_op const>(real);
  if (!p || !p->fun) return NULL;
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(p->fun);
  if (!f) return NULL;
  std::string name = f->rname + std::string("_round_") + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new bnd_float_scheme(real, p->ops[0], name.c_str(), f);
}

// BND(float(a)) <- BND(float(a))
REGISTER_SCHEME_BEGIN(bnd_float_enforce);
  float_rounding_class const *rnd;
  bnd_float_enforce_scheme(ast_real const *r, char const *n, float_rounding_class const *f)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, predicated_real(r, PRED_BND)), n), rnd(f) {}
REGISTER_SCHEME_END(bnd_float_enforce);

void bnd_float_enforce_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval const &i = hyps[0].bnd();
  number a = round_number(lower(i), &rnd->format, &float_format::roundUP);
  number b = round_number(upper(i), &rnd->format, &float_format::roundDN);
  res.bnd() = interval(a, (a <= b) ? b : a);
}

proof_scheme *bnd_float_enforce_scheme::factory(ast_real const *real)
{
  real_op const *p = boost::get<real_op const>(real);
  if (!p || !p->fun) return NULL;
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(p->fun);
  if (!f) return NULL;
  std::string name = f->rname + std::string("_enforce");
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new bnd_float_enforce_scheme(real, name.c_str(), f);
}

// BND(float(a) - a) <- BND(a)
REGISTER_SCHEME_BEGIN(bnd_float_error_of_bnd);
  float_rounding_class const *rnd;
  bool has_wide;
  bnd_float_error_of_bnd_scheme(ast_real const *r, ast_real const *e, float_rounding_class const *f, bool w)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, predicated_real(e, PRED_BND)), ""), rnd(f), has_wide(w) {}
REGISTER_SCHEME_END(bnd_float_error_of_bnd);

void bnd_float_error_of_bnd_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  interval const &i = hyps[0].bnd();
  number const &v1 = lower(i), &v2 = upper(i);
  name = rnd->rname + "_absolute_";
  if (rnd->type == ROUND_ZR) {
    bool w = false;
    interval r1 = zero(), r2 = zero();
    if (v1 < 0) {
      int e_err = exponent(v1, rnd->format);
      int e = e_err + rnd->format.prec - 1;
      if (has_wide && e_err > rnd->format.min_exp &&
          near_pow2(v1, e, e_err - 1, true)) {
        w = true;
        --e_err;
      }
      r1 = from_exponent(e_err, +1);
    }
    if (v2 > 0) {
      int e_err = exponent(v2, rnd->format);
      int e = e_err + rnd->format.prec - 1;
      if (has_wide && e_err > rnd->format.min_exp &&
          near_pow2(v2, e, e_err - 1, true)) {
        w = true;
        --e_err;
      }
      r2 = from_exponent(e_err, -1);
    }
    if (w) name += "wide_";
    res.bnd() = hull(r1, r2);
  } else if (rnd->type == ROUND_AW) {
    interval r1 = zero(), r2 = zero();
    if (v1 < 0) {
      int e_err = exponent(v1, rnd->format);
      r1 = from_exponent(e_err, -1);
    }
    if (v2 > 0) {
      int e_err = exponent(v2, rnd->format);
      r2 = from_exponent(e_err, +1);
    }
    res.bnd() = hull(r1, r2);
  } else {
    int e1 = exponent(v1, rnd->format), e2 = exponent(v2, rnd->format),
        e0 = std::max(e1, e2);
    int e_err = directions[rnd->type].to_nearest ? e0 - 1 : e0;
    int e = e0 + rnd->format.prec - 1;
    if (has_wide && e0 > rnd->format.min_exp &&
        (v1 >= 0 || near_pow2(v1, e, e_err - 1, rnd_near_pow2(rnd->type, true ))) &&
        (v2 <= 0 || near_pow2(v2, e, e_err - 1, rnd_near_pow2(rnd->type, false)))) {
      name += "wide_";
      --e_err;
    }
    res.bnd() = from_exponent(e_err, rnd_global_direction_abs(rnd->type, i, false));
  }
  name += directions[rnd->type].name;
}

proof_scheme *bnd_float_error_of_bnd_scheme::factory(ast_real const *real)
{
  ast_real const *holders[2];
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(absolute_rounding_error(real, holders));
  if (!f || use_abs(f->type)) return NULL;
  std::string name = f->rname + "_absolute_" + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  name = f->rname + "_absolute_wide_" + directions[f->type].name;
  bool w = !is_unknown_theorem(name.c_str());
  return new bnd_float_error_of_bnd_scheme(real, holders[0], f, w);
}

// BND(float(a) - a) <- ABS(a)
REGISTER_SCHEME_BEGIN(bnd_float_error_of_abs);
  float_rounding_class const *rnd;
  bool has_wide;
  bnd_float_error_of_abs_scheme(ast_real const *r, predicated_real const &e, float_rounding_class const *f, bool w)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, e), ""), rnd(f), has_wide(w) {}
REGISTER_SCHEME_END(bnd_float_error_of_abs);

void bnd_float_error_of_abs_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  number const &v = upper(hyps[0].bnd());
  int e0 = exponent(v, rnd->format);
  int e_err = directions[rnd->type].to_nearest ? e0 - 1 : e0;
  int e = e0 + rnd->format.prec - 1;
  name = rnd->rname + "_absolute_";
  if (has_wide && e0 > rnd->format.min_exp &&
      near_pow2(v, e, e_err - 1, rnd_near_pow2_abs(rnd->type))) {
    name += "wide_";
    --e_err;
  }
  name += directions[rnd->type].name;
  res.bnd() = from_exponent(e_err, 0);
}

proof_scheme *bnd_float_error_of_abs_scheme::factory(ast_real const *real)
{
  ast_real const *holders[2];
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(absolute_rounding_error(real, holders));
  if (!f || !use_abs(f->type)) return NULL;
  std::string name = f->rname + "_absolute_" + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  name = f->rname + "_absolute_wide_" + directions[f->type].name;
  bool w = !is_unknown_theorem(name.c_str());
  return new bnd_float_error_of_abs_scheme(real, predicated_real(holders[0], PRED_ABS), f, w);
}

// BND(float(a) - a) <- BND(float(a))
REGISTER_SCHEME_BEGIN(bnd_float_error_of_bnd_float);
  float_rounding_class const *rnd;
  bnd_float_error_of_bnd_float_scheme(ast_real const *r, ast_real const *a, char const *n, float_rounding_class const *f)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, predicated_real(a, PRED_BND)), n), rnd(f) {}
REGISTER_SCHEME_END(bnd_float_error_of_bnd_float);

void bnd_float_error_of_bnd_float_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval const &i = hyps[0].bnd();
  int e1 = exponent(lower(i), rnd->format), e2 = exponent(upper(i), rnd->format);
  int e_err = std::max(e1, e2);
  if (directions[rnd->type].to_nearest) --e_err;
  res.bnd() = from_exponent(e_err, rnd_global_direction_abs(rnd->type, i, true));
}

proof_scheme *bnd_float_error_of_bnd_float_scheme::factory(ast_real const *real)
{
  ast_real const *holders[2];
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(absolute_rounding_error(real, holders));
  if (!f || use_abs(f->type)) return NULL;
  std::string name = f->rname + "_absolute_inv_" + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new bnd_float_error_of_bnd_float_scheme(real, holders[1], name.c_str(), f);
}

// BND(float(a) - a) <- ABS(float(a))
REGISTER_SCHEME_BEGIN(bnd_float_error_of_abs_float);
  float_rounding_class const *rnd;
  bnd_float_error_of_abs_float_scheme(ast_real const *r, predicated_real const &e, char const *n, float_rounding_class const *f)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, e), n), rnd(f) {}
REGISTER_SCHEME_END(bnd_float_error_of_abs_float);

void bnd_float_error_of_abs_float_scheme::compute(property const hyps[], property &res, std::string &) const
{
  int e_err = exponent(upper(hyps[0].bnd()), rnd->format);
  if (directions[rnd->type].to_nearest) --e_err;
  res.bnd() = from_exponent(e_err, 0);
}

proof_scheme *bnd_float_error_of_abs_float_scheme::factory(ast_real const *real)
{
  ast_real const *holders[2];
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(absolute_rounding_error(real, holders));
  if (!f || !use_abs(f->type)) return NULL;
  std::string name = f->rname + "_absolute_inv_" + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new bnd_float_error_of_abs_float_scheme(real, predicated_real(holders[1], PRED_ABS), name.c_str(), f);
}

// REL(floatx(a), a)
REGISTER_SCHEME_BEGIN(rel_floatx);
  float_rounding_class const *rnd;
  rel_floatx_scheme(predicated_real const &r, char const *n, float_rounding_class const *f)
    : proof_scheme(r, preal_vect(), n), rnd(f) {}
REGISTER_SCHEME_END_PREDICATE(rel_floatx);

void rel_floatx_scheme::compute(property const [], property &res, std::string &) const
{
  int e = -rnd->format.prec;
  if (!directions[rnd->type].to_nearest) ++e;
  res.bnd() = from_exponent(e, rnd_global_direction_rel(rnd->type));
}

proof_scheme *rel_floatx_scheme::factory(predicated_real const &real)
{
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(relative_rounding_error(real));
  if (!f || f->format.min_exp != INT_MIN) return NULL;
  std::string name = std::string("floatx_relative_") + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new rel_floatx_scheme(real, name.c_str(), f);
}

// REL(float(a), a) <- ABS(a)
REGISTER_SCHEME_BEGIN(rel_float_of_abs);
  float_rounding_class const *rnd;
  rel_float_of_abs_scheme(predicated_real const &r, char const *n, float_rounding_class const *f)
    : proof_scheme(r, preal_vect(1, predicated_real(r.real2(), PRED_ABS)), n), rnd(f) {}
REGISTER_SCHEME_END_PREDICATE(rel_float_of_abs);

void rel_float_of_abs_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  int es = rnd->format.min_exp + rnd->format.prec - 1;
  bool fail = !is_empty(intersect(hyps[0].bnd(), from_exponent(es, 0)));
  if (fail) {
    if (parameter_constrained) return;
    name = "$FALSE";
  }
  int e = -rnd->format.prec;
  if (!directions[rnd->type].to_nearest) ++e;
  res.bnd() = from_exponent(e, rnd_global_direction_rel(rnd->type)); // cannot use i since it is ABS
}

proof_scheme *rel_float_of_abs_scheme::factory(predicated_real const &real)
{
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(relative_rounding_error(real));
  if (!f || f->format.min_exp == INT_MIN) return NULL;
  std::string name = std::string("float_relative_") + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new rel_float_of_abs_scheme(real, name.c_str(), f);
}

// REL(float(a), a) <- ABS(float(a))
REGISTER_SCHEME_BEGIN(rel_float_of_abs_float);
  float_rounding_class const *rnd;
  rel_float_of_abs_float_scheme(predicated_real const &r, char const *n, float_rounding_class const *f)
    : proof_scheme(r, preal_vect(1, predicated_real(r.real(), PRED_ABS)), n), rnd(f) {}
REGISTER_SCHEME_END_PREDICATE(rel_float_of_abs_float);

void rel_float_of_abs_float_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  int es = rnd->format.min_exp + rnd->format.prec - 1;
  bool fail = !is_empty(intersect(hyps[0].bnd(), from_exponent(es, 0)));
  if (fail) {
    if (parameter_constrained) return;
    name = "$FALSE";
  }
  int e = -rnd->format.prec;
  if (!directions[rnd->type].to_nearest) ++e;
  res.bnd() = from_exponent(e, rnd_global_direction_rel(rnd->type)); // cannot use i since it is ABS
}

proof_scheme *rel_float_of_abs_float_scheme::factory(predicated_real const &real)
{
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(relative_rounding_error(real));
  if (!f || f->format.min_exp == INT_MIN) return NULL;
  std::string name = std::string("float_relative_inv_") + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new rel_float_of_abs_float_scheme(real, name.c_str(), f);
}

// FIX(float(a))
REGISTER_SCHEME_BEGIN(fix_of_float);
  long min_exp;
  fix_of_float_scheme(predicated_real const &r, long e)
    : proof_scheme(r, preal_vect(), "fix_of_float"), min_exp(e) {}
REGISTER_SCHEME_END_PREDICATE(fix_of_float);

void fix_of_float_scheme::compute(property const [], property &res, std::string &) const
{
  res.cst() = min_exp;
}

proof_scheme *fix_of_float_scheme::factory(predicated_real const &real) {
  if (real.pred() != PRED_FIX) return NULL;
  real_op const *r = boost::get< real_op const >(real.real());
  if (!r) return NULL;
  float_rounding_class const *f = dynamic_cast< float_rounding_class const * >(r->fun);
  if (!f || f->format.min_exp == INT_MIN) return NULL;
  return new fix_of_float_scheme(real, f->format.min_exp);
}

// FLT(float(a))
REGISTER_SCHEME_BEGIN(flt_of_float);
  long prec;
  flt_of_float_scheme(predicated_real const &r, long p, char const *n)
    : proof_scheme(r, preal_vect(), n), prec(p) {}
REGISTER_SCHEME_END_PREDICATE(flt_of_float);

void flt_of_float_scheme::compute(property const [], property &res, std::string &) const
{
  res.cst() = prec;
}

proof_scheme *flt_of_float_scheme::factory(predicated_real const &real) {
  if (real.pred() != PRED_FLT) return NULL;
  real_op const *r = boost::get< real_op const >(real.real());
  if (!r) return NULL;
  float_rounding_class const *f = dynamic_cast< float_rounding_class const * >(r->fun);
  if (!f) return NULL;
  char const *name = f->format.min_exp == INT_MIN ? "flt_of_floatx" : "flt_of_float";
  return new flt_of_float_scheme(real, f->format.prec, name);
}

// EQL(float(a), a) <- FIX(a), FLT(a)
REGISTER_SCHEME_BEGIN(float_of_fix_flt);
  preal_vect needed;
  long min_exp, prec;
  float_of_fix_flt_scheme(predicated_real const &r, preal_vect const &v, long e, long p)
    : proof_scheme(r, v, "float_of_fix_flt"), min_exp(e), prec(p) {}
REGISTER_SCHEME_END_PATTERN(float_of_fix_flt, predicated_real(pattern(0), pattern(1), PRED_EQL));

void float_of_fix_flt_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (hyps[0].cst() < min_exp || hyps[1].cst() > prec) res.clear();
}

proof_scheme *float_of_fix_flt_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *r = holders[1];
  real_op const *o = boost::get<real_op const>(holders[0]);
  if (!o || !o->fun || r != o->ops[0]) return NULL;
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(o->fun);
  if (!f || f->format.min_exp == INT_MIN) return NULL;
  preal_vect needed;
  needed.push_back(predicated_real(r, PRED_FIX));
  needed.push_back(predicated_real(r, PRED_FLT));
  return new float_of_fix_flt_scheme(real, needed, f->format.min_exp, f->format.prec);
}

// EQL(floatx(a), a) <- FLT(a)
REGISTER_SCHEME_BEGIN(floatx_of_flt);
  preal_vect needed;
  long prec;
  floatx_of_flt_scheme(predicated_real const &r, preal_vect const &v, long p)
    : proof_scheme(r, v, "floatx_of_flt"), prec(p) {}
REGISTER_SCHEME_END_PATTERN(floatx_of_flt, predicated_real(pattern(0), pattern(1), PRED_EQL));

void floatx_of_flt_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (hyps[0].cst() > prec) res.clear();
}

proof_scheme *floatx_of_flt_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *r = holders[1];
  real_op const *o = boost::get<real_op const>(holders[0]);
  if (!o || !o->fun || r != o->ops[0]) return NULL;
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(o->fun);
  if (!f || f->format.min_exp != INT_MIN) return NULL;
  preal_vect needed;
  needed.push_back(predicated_real(r, PRED_FLT));
  return new floatx_of_flt_scheme(real, needed, f->format.prec);
}

// REL(float(a), a) <- FIX(a)
REGISTER_SCHEME_BEGIN(rel_of_fix_float);
  long min_exp, prec;
  direction_type type;
  rel_of_fix_float_scheme(predicated_real const &r, long e, long p, direction_type t, char const *n)
    : proof_scheme(r, preal_vect(1, predicated_real(r.real2(), PRED_FIX)), n),
      min_exp(e), prec(p), type(t) {}
REGISTER_SCHEME_END_PREDICATE(rel_of_fix_float);

void rel_of_fix_float_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (hyps[0].cst() < min_exp) return;
  res.bnd() = directions[type].to_nearest ? from_exponent(-prec, 0)
    : from_exponent(1 - prec, rnd_global_direction_rel(type));
}

proof_scheme *rel_of_fix_float_scheme::factory(predicated_real const &real)
{
  float_rounding_class const *f = dynamic_cast< float_rounding_class const * >(relative_rounding_error(real));
  if (!f || f->format.min_exp == INT_MIN) return NULL;
  return new rel_of_fix_float_scheme(real, f->format.min_exp, f->format.prec, f->type,
    ("rel_of_fix_float" + std::string(1, ',') + directions[f->type].name).c_str());
}

// LIN(float(a), b) <- LIN(a, b), FLT(b), FIX(a)
REGISTER_SCHEME_BEGIN(lin_float_round_of_lin_flt);
  float_format const &format;
  direction_type type;
  lin_float_round_of_lin_flt_scheme(predicated_real const &r, preal_vect const &v, float_format const &f, direction_type t, char const *n)
    : proof_scheme(r, v, n), format(f), type(t) {}
REGISTER_SCHEME_END_PREDICATE(lin_float_round_of_lin_flt);

void lin_float_round_of_lin_flt_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (hyps[1].cst() > 1) return;
  if (format.min_exp != INT_MIN && hyps[2].cst() < format.min_exp) return;
  interval i = hyps[0].bnd();
  rounding_fun f = directions[type].function;
  number a = round_number(lower(i), &format, f);
  number b = round_number(upper(i), &format, f);
  res.bnd() = interval(a, b);
}

proof_scheme *lin_float_round_of_lin_flt_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_LIN) return NULL;
  real_op const *r = boost::get< real_op const >(real.real());
  if (!r) return NULL;
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(r->fun);
  if (!f || r->ops[0] == real.real2()) return NULL;
  if (!directions[f->type].symmetric) return NULL;
  preal_vect needed;
  needed.push_back(predicated_real(r->ops[0], real.real2(), PRED_LIN));
  needed.push_back(predicated_real(real.real2(), PRED_FLT));
  if (f->format.min_exp != INT_MIN)
    needed.push_back(predicated_real(r->ops[0], PRED_FIX));
  std::string name = f->format.min_exp == INT_MIN ? "lin_floatx_round_of_lin_flt_" : "lin_float_round_of_lin_flt_";
  name += directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  return new lin_float_round_of_lin_flt_scheme(real, needed, f->format, f->type, name.c_str());
}

// LIN(float(a) - a, b) <- LIN(a, b), FLT(b), FIX(a)
REGISTER_SCHEME_BEGIN(lin_float_error_of_lin_flt);
  float_format const &format;
  direction_type type;
  bool has_wide;
lin_float_error_of_lin_flt_scheme(predicated_real const &r, preal_vect const &v, float_format const &f, direction_type t, bool w)
  : proof_scheme(r, v, ""), format(f), type(t), has_wide(w) {}
REGISTER_SCHEME_END_PREDICATE(lin_float_error_of_lin_flt);

void lin_float_error_of_lin_flt_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  if (hyps[1].cst() > 1) return;
  if (format.min_exp != INT_MIN && hyps[2].cst() < format.min_exp) return;
  interval i = abs(hyps[0].bnd());
  number const &v = upper(i);
  int e = exponent(v);
  int e_err = directions[type].to_nearest ? e - format.prec - 1 : e - format.prec;
  name = format.min_exp != INT_MIN ? "lin_floatx_error_of_lin_flt_" : "lin_float_error_of_lin_flt_";
  if (has_wide && near_pow2(v, e - 1, e_err - 1, rnd_near_pow2_abs(type))) {
    name += "wide_";
    --e_err;
  }
  name += directions[type].name;
  res.bnd() = from_exponent(e_err, 0);
}

proof_scheme *lin_float_error_of_lin_flt_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_LIN) return NULL;
  ast_real const *h[2];
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(absolute_rounding_error(real.real(), h));
  if (!f || h[0] == real.real2()) return NULL;
  std::string name_ = f->format.min_exp == INT_MIN ? "lin_floatx_error_of_lin_flt_" : "lin_float_error_of_lin_flt_";
  std::string name = name_ + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  preal_vect needed;
  needed.push_back(predicated_real(h[0], real.real2(), PRED_LIN));
  needed.push_back(predicated_real(real.real2(), PRED_FLT));
  if (f->format.min_exp != INT_MIN)
    needed.push_back(predicated_real(h[0], PRED_FIX));
  name = name_ + "wide_" + directions[f->type].name;
  bool w = !is_unknown_theorem(name.c_str());
  return new lin_float_error_of_lin_flt_scheme(real, needed, f->format, f->type, w);
}

// LIN(float(a) - a, b) <- LIN(a, b), ABS(b)
REGISTER_SCHEME_BEGIN(lin_float_error_of_lin_abs);
  float_format const &format;
  direction_type type;
  lin_float_error_of_lin_abs_scheme(predicated_real const &r, preal_vect const &v, float_format const &f, direction_type t, char const *n)
    : proof_scheme(r, v, n), format(f), type(t) {}
REGISTER_SCHEME_END_PREDICATE(lin_float_error_of_lin_abs);

void lin_float_error_of_lin_abs_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval i = hyps[1].bnd();
  if (contains_zero(i)) return;
  int p = format.prec;
  int emin = format.min_exp;
  if (directions[type].to_nearest) {
    ++p;
    --emin;
  }
  property res2 = res;
  res.bnd() = from_exponent(-p + 1, 0) * hyps[0].bnd();
  res2.bnd() = from_exponent(emin, 0) / i;
  res.hull(res2);
}

proof_scheme *lin_float_error_of_lin_abs_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_LIN) return NULL;
  ast_real const *h[2];
  float_rounding_class const *f = dynamic_cast<float_rounding_class const *>(absolute_rounding_error(real.real(), h));
  if (!f || h[0] == real.real2() || f->format.min_exp == INT_MIN) return NULL;
  std::string name = std::string("lin_float_error_of_lin_abs_") + directions[f->type].name;
  if (is_unknown_theorem(name.c_str())) return NULL;
  preal_vect needed;
  needed.push_back(predicated_real(h[0], real.real2(), PRED_LIN));
  needed.push_back(predicated_real(real.real2(), PRED_ABS));
  return new lin_float_error_of_lin_abs_scheme(real, needed, f->format, f->type, name.c_str());
}

// FIX(float(a)) <- FIX(a)
REGISTER_SCHEME_BEGIN(fix_float_of_fix);
  fix_float_of_fix_scheme(predicated_real const &r, predicated_real const &n,
                          char const *t)
    : proof_scheme(r, preal_vect(1, n), t) {}
REGISTER_SCHEME_END_PREDICATE(fix_float_of_fix);

void fix_float_of_fix_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.cst() = hyps[0].cst();
}

proof_scheme *fix_float_of_fix_scheme::factory(predicated_real const &real) {
  if (real.pred() != PRED_FIX) return NULL;
  real_op const *r = boost::get< real_op const >(real.real());
  if (!r) return NULL;
  float_rounding_class const *f = dynamic_cast< float_rounding_class const * >(r->fun);
  if (!f) return NULL;
  char const *t = f->format.min_exp == INT_MIN ? "fix_floatx_of_fix" : "fix_float_of_fix";
  return new fix_float_of_fix_scheme(real, predicated_real(r->ops[0], PRED_FIX), t);
}

// FLT(float(a)) <- FLT(a)
REGISTER_SCHEME_BEGIN(flt_float_of_flt);
  flt_float_of_flt_scheme(predicated_real const &r, predicated_real const &n,
                          char const *t)
    : proof_scheme(r, preal_vect(1, n), t) {}
REGISTER_SCHEME_END_PREDICATE(flt_float_of_flt);

void flt_float_of_flt_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.cst() = hyps[0].cst();
}

proof_scheme *flt_float_of_flt_scheme::factory(predicated_real const &real) {
  if (real.pred() != PRED_FLT) return NULL;
  real_op const *r = boost::get< real_op const >(real.real());
  if (!r) return NULL;
  float_rounding_class const *f = dynamic_cast< float_rounding_class const * >(r->fun);
  if (!f) return NULL;
  char const *t = f->format.min_exp == INT_MIN ? "flt_floatx_of_flt" : "flt_float_of_flt";
  return new flt_float_of_flt_scheme(real, predicated_real(r->ops[0], PRED_FLT), t);
}
