Skip to content

Commit

Permalink
Implement Numeric#step.size
Browse files Browse the repository at this point in the history
Gets Numeric#step.size doing its thing by putting the following pieces
in place:
* implement Numeric#intervalStepSize closely following the MRI
implementation (https://github.com/ruby/ruby/blob/09b02349c212cac6395e9a634e3d4610e9bbc48c/numeric.c#L1799)
* implement Numeric#floatStepSize which Numeric#intervalStepSize relies
on, again porting from MRI (https://github.com/ruby/ruby/blob/09b02349c212cac6395e9a634e3d4610e9bbc48c/numeric.c#L1748))
* refactor Numeric#floatStep19 to also use the new floatStepSize, which
not only avoids duplication, but also fixes some tests
* create Numeric#stepSize, and use it to enumeratorizeWithSize our steps

We'll be able to un-exclude test_size_for_step from test_enumerator.rb
once Range#size is implemented (jruby#1252).
  • Loading branch information
dmarcotte committed Nov 29, 2013
1 parent b317fbd commit 633f935
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 23 deletions.
125 changes: 104 additions & 21 deletions core/src/main/java/org/jruby/RubyNumeric.java
Expand Up @@ -34,32 +34,30 @@
***** END LICENSE BLOCK *****/
package org.jruby;

import static org.jruby.RubyEnumerator.enumeratorize;
import static org.jruby.util.Numeric.f_abs;
import static org.jruby.util.Numeric.f_arg;
import static org.jruby.util.Numeric.f_mul;
import static org.jruby.util.Numeric.f_negative_p;

import java.math.BigInteger;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.invokedynamic.MethodNames;
import org.jruby.util.ByteList;
import org.jruby.util.ConvertDouble;
import org.jruby.util.ConvertBytes;
import org.jruby.util.ConvertDouble;

import static org.jruby.runtime.Helpers.invokedynamic;
import java.math.BigInteger;

import org.jruby.runtime.invokedynamic.MethodNames;
import static org.jruby.RubyEnumerator.enumeratorizeWithSize;
import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.util.Numeric.f_abs;
import static org.jruby.util.Numeric.f_arg;
import static org.jruby.util.Numeric.f_mul;
import static org.jruby.util.Numeric.f_negative_p;

/**
* Base class for all numerical types in ruby.
Expand Down Expand Up @@ -774,12 +772,20 @@ public IRubyObject truncate() {

@JRubyMethod
public IRubyObject step(ThreadContext context, IRubyObject arg0, Block block) {
return block.isGiven() ? stepCommon(context, arg0, RubyFixnum.one(context.runtime), block) : enumeratorize(context.runtime, this, "step", arg0);
if (block.isGiven()) {
return stepCommon(context, arg0, RubyFixnum.one(context.runtime), block);
} else {
return enumeratorizeWithSize(context, this, "step", new IRubyObject[] { arg0 }, stepSize(context, this, arg0, RubyFixnum.one(context.runtime)));
}
}

@JRubyMethod
public IRubyObject step(ThreadContext context, IRubyObject to, IRubyObject step, Block block) {
return block.isGiven() ? stepCommon(context, to, step, block) : enumeratorize(context.runtime, this, "step", new IRubyObject[] {to, step});
if (block.isGiven()) {
return stepCommon(context, to, step, block);
} else {
return enumeratorizeWithSize(context, this, "step", new IRubyObject[]{to, step}, stepSize(context, this, to, step));
}
}

private IRubyObject stepCommon(ThreadContext context, IRubyObject to, IRubyObject step, Block block) {
Expand Down Expand Up @@ -846,20 +852,19 @@ static void floatStep19(ThreadContext context, Ruby runtime, IRubyObject from, I
double end = num2dbl(to);
double unit = num2dbl(step);

// TODO: remove
if (unit == 0) throw runtime.newArgumentError("step cannot be 0");

double n = (end - beg)/unit;
double err = (Math.abs(beg) + Math.abs(end) + Math.abs(end - beg)) / Math.abs(unit) * DBL_EPSILON;
double n = floatStepSize(beg, end, unit, excl);

if (Double.isInfinite(unit)) {
if (unit > 0) block.yield(context, RubyFloat.newFloat(runtime, beg));
} else {
if (err > 0.5) err = 0.5;
n = Math.floor(n + err);
if (!excl) n++;
for (long i = 0; i < n; i++){
block.yield(context, RubyFloat.newFloat(runtime, i * unit + beg));
double d = i * unit + beg;
if (unit >= 0 ? end < d : d < end) {
d = end;
}
block.yield(context, RubyFloat.newFloat(runtime, d));
}
}
}
Expand All @@ -875,6 +880,84 @@ private static void duckStep(ThreadContext context, Ruby runtime, IRubyObject fr
}
}

public static RubyNumeric intervalStepSize(ThreadContext context, IRubyObject from, IRubyObject to, IRubyObject step, boolean excludeLast) {
Ruby runtime = context.runtime;
if (from instanceof RubyFixnum && to instanceof RubyFixnum && step instanceof RubyFixnum) {
long diff = ((RubyFixnum) step).getLongValue();
long delta = ((RubyFixnum) to).getLongValue() - ((RubyFixnum) from).getLongValue();

if (excludeLast) {
delta += (diff > 0 ? -1 : +1);
}

if (diff == 0) {
return RubyFloat.newFloat(runtime, Double.POSITIVE_INFINITY);
}

long result = delta / diff;
return new RubyFixnum(runtime, result >= 0 ? result + 1 : 0);
} else if (from instanceof RubyFloat || to instanceof RubyFloat || step instanceof RubyFloat) {
double n = floatStepSize(from.convertToFloat().getDoubleValue(), to.convertToFloat().getDoubleValue(), step.convertToFloat().getDoubleValue(), excludeLast);

if (Double.isInfinite(n)) {
return runtime.newFloat(n);
}
return runtime.newFixnum((long) n);
} else {
String cmpString = step.callMethod(context, ">", RubyFixnum.zero(runtime)).isTrue() ? ">" : "<";
if (from.callMethod(context, cmpString, to).isTrue()) {
return RubyFixnum.zero(runtime);
}

IRubyObject diff = to.callMethod(context, "-", from);
IRubyObject result = diff.callMethod(context, "div", step);
if (!excludeLast || from.callMethod(context, "+", result.callMethod(context, "*", step)).callMethod(context, cmpString, to).isTrue()) {
result = result.callMethod(context, "+", RubyFixnum.newFixnum(runtime, 1));
}
return (RubyFixnum) result;
}
}

public static RubyNumeric stepSize(ThreadContext context, IRubyObject from, IRubyObject to, IRubyObject step) {
return intervalStepSize(context, from, to, step, false);
}

/**
* Returns the number of unit-sized steps between the given beg and end.
*
* NOTE: the returned value is either Double.POSITIVE_INFINITY, or a rounded value appropriate to be cast to a long
*/
public static double floatStepSize(double beg, double end, double unit, boolean excludeLast) {
double n = (end - beg)/unit;
double err = (Math.abs(beg) + Math.abs(end) + Math.abs(end - beg)) / Math.abs(unit) * DBL_EPSILON;

if (Double.isInfinite(unit)) {
if (unit > 0) {
return beg <= end ? 1 : 0;
} else {
return end <= beg ? 1 : 0;
}
}

if (err > 0.5) err = 0.5;
if (excludeLast) {
if (n <= 0) {
return 0;
}
if (n < 1) {
n = 0;
} else {
n = Math.floor(n - err);
}
} else {
if (n < 0) {
return 0;
}
n = Math.floor(n + err);
}
return n + 1;
}

/** num_equal, doesn't override RubyObject.op_equal
*
*/
Expand Down
2 changes: 0 additions & 2 deletions test/mri/excludes/TestFloat.rb
Expand Up @@ -4,8 +4,6 @@
exclude :test_invalid_str, "needs investigation"
exclude :test_num2dbl, "needs investigation"
exclude :test_pow, "needs investigation"
exclude :test_step, "needs investigation"
exclude :test_step_excl, "needs investigation"
exclude :test_strtod, "needs investigation"
exclude :test_to_s, "needs investigation"
exclude :test_round_with_precision, "needs investigation"

0 comments on commit 633f935

Please sign in to comment.