/*
 *  call-seq:
 *     num.step(limit, step ) {|i| block }     => num
 *  
 *  Invokes <em>block</em> with the sequence of numbers starting at
 *  <i>num</i>, incremented by <i>step</i> on each call. The loop
 *  finishes when the value to be passed to the block is greater than
 *  <i>limit</i> (if <i>step</i> is positive) or less than
 *  <i>limit</i> (if <i>step</i> is negative). If all the arguments are
 *  integers, the loop operates using an integer counter. If any of the
 *  arguments are floating point numbers, all are converted to floats,
 *  and the loop is executed <i>floor(n + n*epsilon)+ 1</i> times,
 *  where <i>n = (limit - num)/step</i>. Otherwise, the loop
 *  starts at <i>num</i>, uses either the <code><</code> or
 *  <code>></code> operator to compare the counter against
 *  <i>limit</i>, and increments itself using the <code>+</code>
 *  operator.
 *     
 *     1.step(10, 2) { |i| print i, " " }
 *     Math::E.step(Math::PI, 0.2) { |f| print f, " " }
 *     
 *  <em>produces:</em>
 *     
 *     1 3 5 7 9
 *     2.71828182845905 2.91828182845905 3.11828182845905
 */

static VALUE
num_step(argc, argv, from)
    int argc;
    VALUE *argv;
    VALUE from;
{
    VALUE to, step;

    if (argc == 1) {
        to = argv[0];
        step = INT2FIX(1);
    }
    else {
        if (argc == 2) {
            to = argv[0];
            step = argv[1];
        }
        else {
            rb_raise(rb_eArgError, "wrong number of arguments");
        }
        if (rb_equal(step, INT2FIX(0))) {
            rb_raise(rb_eArgError, "step can't be 0");
        }
    }

    if (FIXNUM_P(from) && FIXNUM_P(to) && FIXNUM_P(step)) {
        long i, end, diff;

        i = FIX2LONG(from);
        end = FIX2LONG(to);
        diff = FIX2LONG(step);

        if (diff > 0) {
            while (i <= end) {
                rb_yield(LONG2FIX(i));
                i += diff;
            }
        }
        else {
            while (i >= end) {
                rb_yield(LONG2FIX(i));
                i += diff;
            }
        }
    }
    else if (TYPE(from) == T_FLOAT || TYPE(to) == T_FLOAT || TYPE(step) == T_FLOAT) {
        const double epsilon = DBL_EPSILON;
        double beg = NUM2DBL(from);
        double end = NUM2DBL(to);
        double unit = NUM2DBL(step);
        double n = (end - beg)/unit;
        double err = (fabs(beg) + fabs(end) + fabs(end-beg)) / fabs(unit) * epsilon;
        long i;

        if (err>0.5) err=0.5;
        n = floor(n + err) + 1;
        for (i=0; i<n; i++) {
            rb_yield(rb_float_new(i*unit+beg));
        }
    }
    else {
        VALUE i = from;
        ID cmp;

        if (RTEST(rb_funcall(step, '>', 1, INT2FIX(0)))) {
            cmp = '>';
        }
        else {
            cmp = '<';
        }
        for (;;) {
            if (RTEST(rb_funcall(i, cmp, 1, to))) break;
            rb_yield(i);
            i = rb_funcall(i, '+', 1, step);
        }
    }
    return from;
}