#ifndef INTERNAL_NUMERIC_H                               /*-*-C-*-vi:se ft=c:*/
#define INTERNAL_NUMERIC_H
/**
 * @file
 * @author     Ruby developers <ruby-core@ruby-lang.org>
 * @copyright  This  file  is   a  part  of  the   programming  language  Ruby.
 *             Permission  is hereby  granted,  to  either redistribute  and/or
 *             modify this file, provided that  the conditions mentioned in the
 *             file COPYING are met.  Consult the file for details.
 * @brief      Internal header for Numeric.
 */
#include "internal/bignum.h"    /* for BIGNUM_POSITIVE_P */
#include "internal/bits.h"      /* for RUBY_BIT_ROTL */
#include "internal/fixnum.h"    /* for FIXNUM_POSITIVE_P */
#include "internal/vm.h"        /* for rb_method_basic_definition_p */
#include "ruby/intern.h"        /* for rb_cmperr */
#include "ruby/ruby.h"          /* for USE_FLONUM */

#define ROUND_TO(mode, even, up, down) \
    ((mode) == RUBY_NUM_ROUND_HALF_EVEN ? even : \
     (mode) == RUBY_NUM_ROUND_HALF_UP ? up : down)
#define ROUND_FUNC(mode, name) \
    ROUND_TO(mode, name##_half_even, name##_half_up, name##_half_down)
#define ROUND_CALL(mode, name, args) \
    ROUND_TO(mode, name##_half_even args, \
             name##_half_up args, name##_half_down args)

#ifndef ROUND_DEFAULT
# define ROUND_DEFAULT RUBY_NUM_ROUND_HALF_UP
#endif

enum ruby_num_rounding_mode {
    RUBY_NUM_ROUND_HALF_UP,
    RUBY_NUM_ROUND_HALF_EVEN,
    RUBY_NUM_ROUND_HALF_DOWN,
    RUBY_NUM_ROUND_DEFAULT = ROUND_DEFAULT,
};

struct RFloat {
    struct RBasic basic;
    double float_value;
};

#define RFLOAT(obj)  ((struct RFloat *)(obj))

/* numeric.c */
int rb_num_to_uint(VALUE val, unsigned int *ret);
VALUE ruby_num_interval_step_size(VALUE from, VALUE to, VALUE step, int excl);
double ruby_float_step_size(double beg, double end, double unit, int excl);
int ruby_float_step(VALUE from, VALUE to, VALUE step, int excl, int allow_endless);
int rb_num_negative_p(VALUE);
VALUE rb_int_succ(VALUE num);
VALUE rb_int_uminus(VALUE num);
VALUE rb_float_uminus(VALUE num);
VALUE rb_int_plus(VALUE x, VALUE y);
VALUE rb_float_plus(VALUE x, VALUE y);
VALUE rb_int_minus(VALUE x, VALUE y);
VALUE rb_int_mul(VALUE x, VALUE y);
VALUE rb_float_mul(VALUE x, VALUE y);
VALUE rb_float_div(VALUE x, VALUE y);
VALUE rb_int_idiv(VALUE x, VALUE y);
VALUE rb_int_modulo(VALUE x, VALUE y);
VALUE rb_int2str(VALUE num, int base);
VALUE rb_fix_plus(VALUE x, VALUE y);
VALUE rb_int_gt(VALUE x, VALUE y);
VALUE rb_float_gt(VALUE x, VALUE y);
VALUE rb_int_ge(VALUE x, VALUE y);
enum ruby_num_rounding_mode rb_num_get_rounding_option(VALUE opts);
double rb_int_fdiv_double(VALUE x, VALUE y);
VALUE rb_int_pow(VALUE x, VALUE y);
VALUE rb_float_pow(VALUE x, VALUE y);
VALUE rb_int_cmp(VALUE x, VALUE y);
VALUE rb_int_equal(VALUE x, VALUE y);
VALUE rb_int_divmod(VALUE x, VALUE y);
VALUE rb_int_and(VALUE x, VALUE y);
VALUE rb_int_lshift(VALUE x, VALUE y);
VALUE rb_int_div(VALUE x, VALUE y);
int rb_int_positive_p(VALUE num);
int rb_int_negative_p(VALUE num);
VALUE rb_num_pow(VALUE x, VALUE y);
VALUE rb_float_ceil(VALUE num, int ndigits);
VALUE rb_float_abs(VALUE flt);
static inline VALUE rb_num_compare_with_zero(VALUE num, ID mid);
static inline int rb_num_positive_int_p(VALUE num);
static inline int rb_num_negative_int_p(VALUE num);
static inline double rb_float_flonum_value(VALUE v);
static inline double rb_float_noflonum_value(VALUE v);
static inline double rb_float_value_inline(VALUE v);
static inline VALUE rb_float_new_inline(double d);
static inline bool INT_POSITIVE_P(VALUE num);
static inline bool INT_NEGATIVE_P(VALUE num);
static inline bool FLOAT_ZERO_P(VALUE num);
#define rb_float_value rb_float_value_inline
#define rb_float_new   rb_float_new_inline

RUBY_SYMBOL_EXPORT_BEGIN
/* numeric.c (export) */
VALUE rb_int_positive_pow(long x, unsigned long y);
RUBY_SYMBOL_EXPORT_END

MJIT_SYMBOL_EXPORT_BEGIN
VALUE rb_flo_div_flo(VALUE x, VALUE y);
double ruby_float_mod(double x, double y);
VALUE rb_float_equal(VALUE x, VALUE y);
int rb_float_cmp(VALUE x, VALUE y);
VALUE rb_float_eql(VALUE x, VALUE y);
VALUE rb_fix_aref(VALUE fix, VALUE idx);
VALUE rb_int_zero_p(VALUE num);
VALUE rb_int_even_p(VALUE num);
VALUE rb_int_odd_p(VALUE num);
VALUE rb_int_abs(VALUE num);
VALUE rb_int_bit_length(VALUE num);
MJIT_SYMBOL_EXPORT_END

static inline bool
INT_POSITIVE_P(VALUE num)
{
    if (FIXNUM_P(num)) {
        return FIXNUM_POSITIVE_P(num);
    }
    else {
        return BIGNUM_POSITIVE_P(num);
    }
}

static inline bool
INT_NEGATIVE_P(VALUE num)
{
    if (FIXNUM_P(num)) {
        return FIXNUM_NEGATIVE_P(num);
    }
    else {
        return BIGNUM_NEGATIVE_P(num);
    }
}

static inline bool
FLOAT_ZERO_P(VALUE num)
{
    return RFLOAT_VALUE(num) == 0.0;
}

static inline VALUE
rb_num_compare_with_zero(VALUE num, ID mid)
{
    VALUE zero = INT2FIX(0);
    VALUE r = rb_check_funcall(num, mid, 1, &zero);
    if (r == Qundef) {
        rb_cmperr(num, zero);
    }
    return r;
}

static inline int
rb_num_positive_int_p(VALUE num)
{
    const ID mid = '>';

    if (FIXNUM_P(num)) {
        if (rb_method_basic_definition_p(rb_cInteger, mid))
            return FIXNUM_POSITIVE_P(num);
    }
    else if (RB_TYPE_P(num, T_BIGNUM)) {
        if (rb_method_basic_definition_p(rb_cInteger, mid))
            return BIGNUM_POSITIVE_P(num);
    }
    return RTEST(rb_num_compare_with_zero(num, mid));
}

static inline int
rb_num_negative_int_p(VALUE num)
{
    const ID mid = '<';

    if (FIXNUM_P(num)) {
        if (rb_method_basic_definition_p(rb_cInteger, mid))
            return FIXNUM_NEGATIVE_P(num);
    }
    else if (RB_TYPE_P(num, T_BIGNUM)) {
        if (rb_method_basic_definition_p(rb_cInteger, mid))
            return BIGNUM_NEGATIVE_P(num);
    }
    return RTEST(rb_num_compare_with_zero(num, mid));
}

static inline double
rb_float_flonum_value(VALUE v)
{
#if USE_FLONUM
    if (v != (VALUE)0x8000000000000002) { /* LIKELY */
        union {
            double d;
            VALUE v;
        } t;

        VALUE b63 = (v >> 63);
        /* e: xx1... -> 011... */
        /*    xx0... -> 100... */
        /*      ^b63           */
        t.v = RUBY_BIT_ROTR((2 - b63) | (v & ~(VALUE)0x03), 3);
        return t.d;
    }
#endif
    return 0.0;
}

static inline double
rb_float_noflonum_value(VALUE v)
{
    return RFLOAT(v)->float_value;
}

static inline double
rb_float_value_inline(VALUE v)
{
    if (FLONUM_P(v)) {
        return rb_float_flonum_value(v);
    }
    return rb_float_noflonum_value(v);
}

static inline VALUE
rb_float_new_inline(double d)
{
#if USE_FLONUM
    union {
        double d;
        VALUE v;
    } t;
    int bits;

    t.d = d;
    bits = (int)((VALUE)(t.v >> 60) & 0x7);
    /* bits contains 3 bits of b62..b60. */
    /* bits - 3 = */
    /*   b011 -> b000 */
    /*   b100 -> b001 */

    if (t.v != 0x3000000000000000 /* 1.72723e-77 */ &&
        !((bits-3) & ~0x01)) {
        return (RUBY_BIT_ROTL(t.v, 3) & ~(VALUE)0x01) | 0x02;
    }
    else if (t.v == (VALUE)0) {
        /* +0.0 */
        return 0x8000000000000002;
    }
    /* out of range */
#endif
    return rb_float_new_in_heap(d);
}

#endif /* INTERNAL_NUMERIC_H */
