Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2d078e3dea0b
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 28eb942fc951
Choose a head ref
  • 3 commits
  • 2 files changed
  • 2 contributors

Commits on Oct 30, 2017

  1. cleanup BigDecimal from the dual 1.8/1.9 Ruby support heritage

    + refactored/simplified div/quo implementation - was hard to manage
    kares authored and headius committed Oct 30, 2017
    Copy the full SHA
    f386054 View commit details
  2. Copy the full SHA
    dc7b498 View commit details
  3. Use instanceof

    MrBerg authored and headius committed Oct 30, 2017
    Copy the full SHA
    28eb942 View commit details
Showing with 136 additions and 121 deletions.
  1. +120 −120 core/src/main/java/org/jruby/ext/bigdecimal/RubyBigDecimal.java
  2. +16 −1 spec/ruby/library/bigdecimal/limit_spec.rb
240 changes: 120 additions & 120 deletions core/src/main/java/org/jruby/ext/bigdecimal/RubyBigDecimal.java
Original file line number Diff line number Diff line change
@@ -46,12 +46,10 @@
import org.jruby.RubyInteger;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import static org.jruby.RubyNumeric.num2int;
import org.jruby.RubyObject;
import org.jruby.RubyRational;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyConstant;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Arity;
@@ -61,7 +59,6 @@
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import static org.jruby.runtime.builtin.IRubyObject.NULL_ARRAY;
import org.jruby.util.Numeric;
import org.jruby.util.SafeDoubleParser;
import org.jruby.util.StringSupport;
@@ -667,7 +664,7 @@ private RubyBigDecimal setResult() {

private RubyBigDecimal setResult(int scale) {
int prec = RubyFixnum.fix2int(getRuntime().getClass("BigDecimal").searchInternalModuleVariable("vpPrecLimit"));
int prec2 = Math.max(scale, prec);
int prec2 = (scale == 0) ? prec : scale;
if (prec2 > 0 && this.value.scale() > (prec2-getExponent())) {
this.value = this.value.setScale(prec2-getExponent(), BigDecimal.ROUND_HALF_UP);
}
@@ -701,12 +698,8 @@ public IRubyObject initialize_copy(IRubyObject original) {
return this;
}

public IRubyObject op_mod(ThreadContext context, IRubyObject arg) {
return op_mod19(context, arg);
}

@JRubyMethod(name = {"%", "modulo"}, required = 1)
public IRubyObject op_mod19(ThreadContext context, IRubyObject other) {
public IRubyObject op_mod(ThreadContext context, IRubyObject other) {
// TODO: full-precision divmod is 1000x slower than MRI!
RubyBigDecimal val = getVpValue19(context, other, false);

@@ -724,14 +717,20 @@ public IRubyObject op_mod19(ThreadContext context, IRubyObject other) {
return new RubyBigDecimal(context.runtime, modulo).setResult();
}

@Deprecated
public IRubyObject op_mod19(ThreadContext context, IRubyObject arg) {
return op_mod(context, arg);
}

@Override
@JRubyMethod(name = "remainder", required = 1)
public IRubyObject remainder(ThreadContext context, IRubyObject arg) {
return remainder19(context, arg);
return remainderInternal(context, getVpValue19(context, arg, false), arg);
}

@JRubyMethod(name = "remainder", required = 1)
@Deprecated
public IRubyObject remainder19(ThreadContext context, IRubyObject arg) {
return remainderInternal(context, getVpValue19(context, arg, false), arg);
return remainder(context, arg);
}

private IRubyObject remainderInternal(ThreadContext context, RubyBigDecimal val, IRubyObject arg) {
@@ -744,21 +743,18 @@ private IRubyObject remainderInternal(ThreadContext context, RubyBigDecimal val,
return new RubyBigDecimal(context.runtime, value.remainder(val.value)).setResult();
}

@JRubyMethod(name = "*", required = 1)
public IRubyObject op_mul(ThreadContext context, IRubyObject arg) {
return op_mul19(context, arg);
return mult2(context, arg, vpPrecLimit(context.runtime));
}

@JRubyMethod(name = "*", required = 1)
@Deprecated
public IRubyObject op_mul19(ThreadContext context, IRubyObject arg) {
return mult219(context, arg, vpPrecLimit(context.runtime));
}

public IRubyObject mult2(ThreadContext context, IRubyObject b, IRubyObject n) {
return mult219(context, b, n);
return op_mul(context, arg);
}

@JRubyMethod(name = "mult", required = 2)
public IRubyObject mult219(ThreadContext context, IRubyObject b, IRubyObject n) {
public IRubyObject mult2(ThreadContext context, IRubyObject b, IRubyObject n) {
RubyBigDecimal val = getVpValue19(context, b, false);
if (val == null) { // TODO: what about n arg?
return callCoerced(context, sites(context).op_times, b, true);
@@ -791,6 +787,11 @@ private RubyBigDecimal multInternal(final Ruby runtime, RubyBigDecimal val, IRub
return new RubyBigDecimal(runtime, res).setResult();
}

@Deprecated
public IRubyObject mult219(ThreadContext context, IRubyObject b, IRubyObject n) {
return mult2(context, b, n);
}

// Calculate appropriate zero or infinity depending on exponent...
private RubyBigDecimal newPowOfInfinity(ThreadContext context, IRubyObject exp) {
if (Numeric.f_negative_p(context, exp)) {
@@ -815,20 +816,11 @@ private static IRubyObject vpPrecLimit(final Ruby runtime) {

// @Deprecated
public IRubyObject op_pow(IRubyObject arg) {
return op_pow19(getRuntime().getCurrentContext(), arg);
}

public RubyBigDecimal op_pow(final ThreadContext context, IRubyObject arg) {
return op_pow19(context, arg);
}

// @Deprecated
public IRubyObject op_pow19(IRubyObject exp) {
return op_pow19(getRuntime().getCurrentContext(), exp);
return op_pow(getRuntime().getCurrentContext(), arg);
}

@JRubyMethod(name = {"**", "power"}, required = 1)
public RubyBigDecimal op_pow19(ThreadContext context, IRubyObject exp) {
public RubyBigDecimal op_pow(final ThreadContext context, IRubyObject exp) {
final Ruby runtime = context.runtime;

if ( ! (exp instanceof RubyNumeric) ) {
@@ -869,29 +861,41 @@ public RubyBigDecimal op_pow19(ThreadContext context, IRubyObject exp) {
return new RubyBigDecimal(runtime, pow);
}

@Deprecated
public IRubyObject op_pow19(IRubyObject exp) {
return op_pow(getRuntime().getCurrentContext(), exp);
}

@Deprecated
public RubyBigDecimal op_pow19(ThreadContext context, IRubyObject exp) {
return op_pow(context, exp);
}

private BigDecimal powNegative(final int times) {
// Note: MRI has a very non-trivial way of calculating the precision,
// so we use very simple approximation here:
int precision = (-times + 4) * (getAllDigits().length() + 4);
return value.pow(times, new MathContext(precision, RoundingMode.HALF_UP));
}

@JRubyMethod(name = "+")
public IRubyObject op_plus(ThreadContext context, IRubyObject b) {
return op_plus19(context, b);
return addInternal(context, getVpValue19(context, b, false), b, vpPrecLimit(context.runtime));
}

@JRubyMethod(name = "+")
@Deprecated
public IRubyObject op_plus19(ThreadContext context, IRubyObject b) {
return addInternal(context, getVpValue19(context, b, false), b, vpPrecLimit(context.runtime));
return op_plus(context, b);
}

@JRubyMethod(name = "add")
public IRubyObject add2(ThreadContext context, IRubyObject b, IRubyObject digits) {
return add219(context, b, digits);
return addInternal(context, getVpValue19(context, b, false), b, digits);
}

@JRubyMethod(name = "add")
@Deprecated
public IRubyObject add219(ThreadContext context, IRubyObject b, IRubyObject digits) {
return addInternal(context, getVpValue19(context, b, false), b, digits);
return add2(context, b, digits);
}

private IRubyObject addInternal(ThreadContext context, RubyBigDecimal val, IRubyObject b, IRubyObject digits) {
@@ -956,31 +960,41 @@ public IRubyObject op_uplus() {
return this;
}

public IRubyObject op_minus(ThreadContext context, IRubyObject b) {
return op_minus19(context, b);
@Override
@JRubyMethod(name = "-@")
public IRubyObject op_uminus(ThreadContext context) {
if (isNaN()) return newNaN(context.runtime);
if (isInfinity()) return newInfinity(context.runtime, -infinitySign);
if (isZero()) return newZero(context.runtime, -zeroSign);

return new RubyBigDecimal(context.runtime, value.negate());
}

@JRubyMethod(name = "-", required = 1)
public IRubyObject op_minus(ThreadContext context, IRubyObject b) {
return subInternal(context, getVpValue19(context, b, true), b, RubyInteger.int2fix(getRuntime(),0));
}

@Deprecated
public IRubyObject op_minus19(ThreadContext context, IRubyObject b) {
return subInternal(context, getVpValue19(context, b, true), b);
return op_minus(context, b);
}

@JRubyMethod(name = "sub", required = 2)
public IRubyObject sub2(ThreadContext context, IRubyObject b, IRubyObject n) {
return sub219(context, b, n);
return subInternal(context, getVpValue19(context, b, false), b, n);
}

@JRubyMethod(name = "sub", required = 2)
@Deprecated
public IRubyObject sub219(ThreadContext context, IRubyObject b, IRubyObject n) {
// FIXME: Missing handling of n
return subInternal(context, getVpValue19(context, b, false), b);
return sub2(context, b, n);
}

private IRubyObject subInternal(ThreadContext context, RubyBigDecimal val, IRubyObject b) {
private IRubyObject subInternal(ThreadContext context, RubyBigDecimal val, IRubyObject b, IRubyObject n) {
if (val == null) return callCoerced(context, sites(context).op_minus, b);

int prec = getPositiveInt(context, n);
RubyBigDecimal res = handleMinusSpecialValues(context, val);

return res != null ? res : new RubyBigDecimal(context.runtime, value.subtract(val.value)).setResult();
return res != null ? res : new RubyBigDecimal(context.runtime, value.subtract(val.value)).setResult(prec);
}

private RubyBigDecimal handleMinusSpecialValues(ThreadContext context, RubyBigDecimal val) {
@@ -1010,33 +1024,13 @@ private RubyBigDecimal handleMinusSpecialValues(ThreadContext context, RubyBigDe
return null;
}

@JRubyMethod(name = "-@")
@Override
public IRubyObject op_uminus(ThreadContext context) {
if (isNaN()) return newNaN(context.runtime);
if (isInfinity()) return newInfinity(context.runtime, -infinitySign);
if (isZero()) return newZero(context.runtime, -zeroSign);

return new RubyBigDecimal(getRuntime(), value.negate());
}

public IRubyObject op_quo(ThreadContext context, IRubyObject other) {
return op_quo20(context, other);
}

public IRubyObject op_quo19(ThreadContext context, IRubyObject other) {
return op_quo19_20(context, other);
}

@JRubyMethod(name = {"/", "quo"})
public IRubyObject op_quo20(ThreadContext context, IRubyObject other) {
return op_quo19_20(context, other);
}

private IRubyObject op_quo19_20(ThreadContext context, IRubyObject other) {
public IRubyObject op_quo(ThreadContext context, IRubyObject other) {
RubyBigDecimal val = getVpValue19(context, other, false);
if (val == null) return callCoerced(context, sites(context).op_quo, other, true);

if (isNaN() || val.isNaN()) return newNaN(context.runtime);

// regular division with some default precision
// proper algorithm to set the precision
// the precision is multiple of 4
@@ -1045,70 +1039,82 @@ private IRubyObject op_quo19_20(ThreadContext context, IRubyObject other) {
int pow = len / 4;
int precision = (pow + 1) * 4 * 2;

return op_div(context, val, context.runtime.newFixnum(precision));
IRubyObject result = divWithScale(context, val, precision);
if (result instanceof RubyBigDecimal) return ((RubyBigDecimal) result).setResult();
return result;
}

public IRubyObject op_div(ThreadContext context, IRubyObject other) {
// integer division
RubyBigDecimal val = getVpValue(context, other, false);
if (val == null) return callCoerced(context, sites(context).div, other);
if (isNaN() || val.isZero() || val.isNaN()) return newNaN(context.runtime);
if (isInfinity() || val.isInfinity()) return newNaN(context.runtime);
@Deprecated
public IRubyObject op_quo19(ThreadContext context, IRubyObject other) {
return op_quo(context, other);
}

return new RubyBigDecimal(context.runtime,
this.value.divideToIntegralValue(val.value)).setResult();
@Deprecated
public IRubyObject op_quo20(ThreadContext context, IRubyObject other) {
return op_quo(context, other);
}

@JRubyMethod(name = "div")
public IRubyObject op_div19(ThreadContext context, IRubyObject r) {
public IRubyObject op_div(ThreadContext context, IRubyObject r) {
RubyBigDecimal val = getVpValue19(context, r, false);
if (val == null) return callCoerced(context, sites(context).div, r, true);

if (isNaN() || val.isNaN()) throw context.runtime.newFloatDomainError("Computation results to 'NaN'");
if (isInfinity() && val.isOne()) throw context.runtime.newFloatDomainError("Computation results to 'Infinity'");
if (isInfinity()) { // NOTE: MRI is inconsistent with div(r, d) impl
if (val.isInfinity()) {
throw context.runtime.newFloatDomainError("Computation results to 'NaN'(Not a Number)");
}
throw context.runtime.newFloatDomainError("Computation results to 'Infinity'");
}
if (val.isInfinity()) return newZero(context.runtime, value.signum() * val.infinitySign);

if (val.isInfinity()) return newZero(context.runtime, val.infinitySign);
if (val.isZero()) throw context.runtime.newZeroDivisionError();

if (isZero() || val.isZero()) throw context.runtime.newZeroDivisionError();
return new RubyBigDecimal(context.runtime, this.value.divideToIntegralValue(val.value)).setResult();
}

@Deprecated
public final IRubyObject op_div19(ThreadContext context, IRubyObject r) {
return op_div(context, r);
}

@JRubyMethod(name = "div")
public IRubyObject op_div(ThreadContext context, IRubyObject other, IRubyObject digits) {
// TODO: take BigDecimal.mode into account.

int scale = RubyNumeric.fix2int(digits);

RubyBigDecimal val = getVpValue(context, other, false);
if (val == null) return callCoerced(context, sites(context).div, other, true);
if (isNaN() || (isZero() && val.isZero()) || val.isNaN()) return newNaN(context.runtime);

if (isNaN() || val.isNaN()) {
throw context.runtime.newFloatDomainError("Computation results to 'NaN'");
}
int prec = RubyNumeric.fix2int(digits);
IRubyObject result = divWithScale(context, val, prec);
if (result instanceof RubyBigDecimal) return ((RubyBigDecimal) result).setResult(prec);
return result;
}

private IRubyObject divWithScale(ThreadContext context, RubyBigDecimal val, int scale) {
if (isInfinity()) {
if (val.isInfinity()) return newNaN(context.runtime);
return newInfinity(context.runtime, infinitySign * val.value.signum());
}
if (val.isInfinity()) return newZero(context.runtime, value.signum() * val.infinitySign);

if (val.isZero()) {
if (isZero()) return newNaN(context.runtime);
int sign1 = isInfinity() ? infinitySign : value.signum();
return newInfinity(context.runtime, sign1 * val.zeroSign);
}

if (isInfinity() && !val.isInfinity()) return newInfinity(context.runtime, infinitySign * val.value.signum());
if (!isInfinity() && val.isInfinity()) return newZero(context.runtime, value.signum() * val.infinitySign);
if (isInfinity() && val.isInfinity()) return newNaN(context.runtime);
if (isZero()) return newZero(context.runtime, zeroSign * val.value.signum());

// MRI behavior: "If digits is 0, the result is the same as the / operator."
if (scale == 0) return op_quo(context, other);
if (scale == 0) return op_quo(context, val);

MathContext mathContext = new MathContext(scale, getRoundingMode(context.runtime));
return new RubyBigDecimal(context.runtime, value.divide(val.value, mathContext)).setResult(scale);
}

@JRubyMethod(name = "div")
public IRubyObject op_div19(ThreadContext context, IRubyObject other, IRubyObject digits) {
RubyBigDecimal val = getVpValue(context, other, false);
if (val == null) return callCoerced(context, sites(context).div, other, true);

if (isNaN() || val.isNaN()) {
throw context.runtime.newFloatDomainError("Computation results to 'NaN'");
}

@Deprecated
public final IRubyObject op_div19(ThreadContext context, IRubyObject other, IRubyObject digits) {
return op_div(context, other, digits);
}

@@ -1126,17 +1132,16 @@ private IRubyObject cmp(ThreadContext context, final IRubyObject arg, final char
} else {
if (isNaN() || rb.isNaN()) return (op == '*') ? context.nil : context.runtime.getFalse();

e = infinitySign != 0 || rb.infinitySign != 0 ?
infinitySign - rb.infinitySign : value.compareTo(rb.value);
e = infinitySign != 0 || rb.infinitySign != 0 ? infinitySign - rb.infinitySign : value.compareTo(rb.value);
}
switch(op) {
case '*': return context.runtime.newFixnum(e);
case '=': return context.runtime.newBoolean(e == 0);
case '!': return context.runtime.newBoolean(e != 0);
case 'G': return context.runtime.newBoolean(e >= 0);
case '>': return context.runtime.newBoolean(e > 0);
case 'L': return context.runtime.newBoolean(e <= 0);
case '<': return context.runtime.newBoolean(e < 0);
case '*': return context.runtime.newFixnum(e);
case '=': return context.runtime.newBoolean(e == 0);
case '!': return context.runtime.newBoolean(e != 0);
case 'G': return context.runtime.newBoolean(e >= 0);
case '>': return context.runtime.newBoolean(e > 0);
case 'L': return context.runtime.newBoolean(e <= 0);
case '<': return context.runtime.newBoolean(e < 0);
}
return context.nil;
}
@@ -1189,7 +1194,7 @@ public IRubyObject ceil(ThreadContext context, IRubyObject arg) {

if (value.scale() <= n) return this; // no rounding necessary

return new RubyBigDecimal(getRuntime(), value.setScale(n, RoundingMode.CEILING));
return new RubyBigDecimal(context.runtime, value.setScale(n, RoundingMode.CEILING));
}

@JRubyMethod
@@ -1250,14 +1255,9 @@ public RubyNumeric multiplyWith(ThreadContext context, RubyBignum value) {
return (RubyNumeric)op_mul(context, value);
}

@Override
public IRubyObject divmod(ThreadContext context, IRubyObject other) {
return divmod19(context, other);
}

@Override
@JRubyMethod(name = "divmod")
public IRubyObject divmod19(ThreadContext context, IRubyObject other) {
public IRubyObject divmod(ThreadContext context, IRubyObject other) {
// TODO: full-precision divmod is 1000x slower than MRI!
Ruby runtime = context.runtime;
RubyBigDecimal val = getVpValue19(context, other, false);
@@ -1340,7 +1340,7 @@ public IRubyObject inspect(ThreadContext context) {
val.append( getSignificantDigits().length() ).append('(');
val.append( ((getAllDigits().length() / 4) + 1) * 4 ).append(')').append('>');

return getRuntime().newString(val.toString());
return RubyString.newString(context.runtime, val);
}

@JRubyMethod(name = "nan?")
17 changes: 16 additions & 1 deletion spec/ruby/library/bigdecimal/limit_spec.rb
Original file line number Diff line number Diff line change
@@ -11,20 +11,35 @@
BigDecimal.limit(old)
end

it "use the global limit if no precision is specified" do
it "uses the global limit if no precision is specified" do
BigDecimalSpecs.with_limit(0) do
(BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.888')
(BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.888')
(BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.664')
(BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.296')
end

BigDecimalSpecs.with_limit(1) do
(BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.9')
(BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.9')
(BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('3')
(BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.3')
end

BigDecimalSpecs.with_limit(2) do
(BigDecimal('0.888') + BigDecimal('0')).should == BigDecimal('0.89')
(BigDecimal('0.888') - BigDecimal('0')).should == BigDecimal('0.89')
(BigDecimal('0.888') * BigDecimal('3')).should == BigDecimal('2.7')
(BigDecimal('0.888') / BigDecimal('3')).should == BigDecimal('0.30')
end
end

it "picks the specified precision over global limit" do
BigDecimalSpecs.with_limit(3) do
BigDecimal('0.888').add(BigDecimal('0'), 2).should == BigDecimal('0.89')
BigDecimal('0.888').sub(BigDecimal('0'), 2).should == BigDecimal('0.89')
BigDecimal('0.888').mult(BigDecimal('3'), 2).should == BigDecimal('2.7')
BigDecimal('0.888').div(BigDecimal('3'), 2).should == BigDecimal('0.30')
end
end
end