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: 5c5b5cb3e24f
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 967ca4064d33
Choose a head ref
  • 5 commits
  • 2 files changed
  • 1 contributor

Commits on Jun 4, 2015

  1. minor BigDecimal code cleanup

    kares committed Jun 4, 2015
    Copy the full SHA
    fdf5201 View commit details
  2. Copy the full SHA
    2b990bc View commit details
  3. Copy the full SHA
    4ac1c5d View commit details
  4. Copy the full SHA
    e492b3a View commit details
  5. basic BigDecimal sub-class test ... all working the same as MRI 1.8/1.9

    ... no need to address #365 as the allocate+initialize sequence seems MRI-like
    kares committed Jun 4, 2015
    Copy the full SHA
    967ca40 View commit details
Showing with 171 additions and 133 deletions.
  1. +122 −131 core/src/main/java/org/jruby/ext/bigdecimal/RubyBigDecimal.java
  2. +49 −2 test/test_big_decimal.rb
253 changes: 122 additions & 131 deletions core/src/main/java/org/jruby/ext/bigdecimal/RubyBigDecimal.java
Original file line number Diff line number Diff line change
@@ -69,8 +69,9 @@
*/
@JRubyClass(name="BigDecimal", parent="Numeric")
public class RubyBigDecimal extends RubyNumeric {
private static final ObjectAllocator BIGDECIMAL_ALLOCATOR = new ObjectAllocator() {
public IRubyObject allocate(Ruby runtime, RubyClass klass) {

private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
public RubyBigDecimal allocate(Ruby runtime, RubyClass klass) {
return new RubyBigDecimal(runtime, klass);
}
};
@@ -126,7 +127,7 @@ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
private static final double SQRT_10 = 3.162277660168379332;

public static RubyClass createBigDecimal(Ruby runtime) {
RubyClass bigDecimal = runtime.defineClass("BigDecimal", runtime.getNumeric(), BIGDECIMAL_ALLOCATOR);
RubyClass bigDecimal = runtime.defineClass("BigDecimal", runtime.getNumeric(), ALLOCATOR);

runtime.getKernel().defineAnnotatedMethods(BigDecimalKernelMethods.class);

@@ -217,9 +218,11 @@ public RubyBigDecimal(Ruby runtime, RubyClass klass, RubyBigDecimal rbd) {
}

public static class BigDecimalKernelMethods {
@JRubyMethod(name = "BigDecimal", rest = true, module = true, visibility = Visibility.PRIVATE)
public static IRubyObject newBigDecimal(IRubyObject recv, IRubyObject[] args) {
return RubyBigDecimal.newBigDecimal(recv, args, Block.NULL_BLOCK);
@JRubyMethod(name = "BigDecimal", required = 1, optional = 1, module = true, visibility = Visibility.PRIVATE)
public static IRubyObject newBigDecimal(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
if (args.length == 1) return newInstance(context, context.runtime.getClass("BigDecimal"), args[0]);

return newInstance(context, context.runtime.getClass("BigDecimal"), args[0], args[1]);
}
}

@@ -418,14 +421,6 @@ private static String errMessageType(ThreadContext context, IRubyObject value) {
return value.getMetaClass().getBaseName();
}

private static RubyBigDecimal unableToCoerceWithoutPrec(ThreadContext context, IRubyObject value, boolean must) {
if (must) {
throw context.runtime.newArgumentError(value.getMetaClass().getBaseName() + " can't be coerced into BigDecimal without a precision");
}

return null;
}

private static RubyBigDecimal getVpValue19(ThreadContext context, IRubyObject v, boolean must) {
long precision;
if (v instanceof RubyFloat) {
@@ -454,19 +449,26 @@ private static RubyBigDecimal getVpValueWithPrec19(ThreadContext context, IRubyO
if (value instanceof RubyFloat) {
if (precision > Long.MAX_VALUE) cannotBeCoerced(context, value, must);

RubyFloat f = (RubyFloat)value;
RubyFloat f = (RubyFloat) value;
value = new RubyBigDecimal(context.runtime, BigDecimal.valueOf(f.getDoubleValue()));
continue;
} else if (value instanceof RubyRational) {
if (precision < 0) return unableToCoerceWithoutPrec(context, value, must);
}
else if (value instanceof RubyRational) {
if (precision < 0) {
if (must) {
throw context.runtime.newArgumentError(value.getMetaClass().getBaseName() + " can't be coerced into BigDecimal without a precision");
}
return null;
}

value = getVpRubyObjectWithPrec19Inner(context, (RubyRational) value, precision, must);
continue;
} else if (value instanceof RubyBigDecimal) {
}
else if (value instanceof RubyBigDecimal) {
return (RubyBigDecimal) value;
} else if (value instanceof RubyFixnum || value instanceof RubyBignum) {
String s = value.toString();
return newInstance(value.getRuntime().getClass("BigDecimal"), new IRubyObject[]{value.getRuntime().newString(s)});
}
else if (value instanceof RubyFixnum || value instanceof RubyBignum) {
return newInstance(context, context.runtime.getClass("BigDecimal"), value.asString());
}

return cannotBeCoerced(context, value, must);
@@ -476,8 +478,7 @@ private static RubyBigDecimal getVpValueWithPrec19(ThreadContext context, IRubyO
private static RubyBigDecimal getVpValue(ThreadContext context, IRubyObject value, boolean must) {
if (value instanceof RubyBigDecimal) return (RubyBigDecimal) value;
if (value instanceof RubyFixnum || value instanceof RubyBignum) {
IRubyObject str = value.callMethod(context, "to_s");
return newInstance(context.runtime.getClass("BigDecimal"), new IRubyObject[] { str });
return newInstance(context, context.runtime.getClass("BigDecimal"), value.asString());
}
return cannotBeCoerced(context, value, must);
}
@@ -487,137 +488,127 @@ public static IRubyObject induced_from(ThreadContext context, IRubyObject recv,
return getVpValue(context, arg, true);
}

private final static Pattern INFINITY_PATTERN = Pattern.compile("^([+-])?Infinity$");
private final static Pattern NUMBER_PATTERN = Pattern.compile("^([+-]?\\d*\\.?\\d*([eE][+-]?)?\\d*).*");
private static RubyBigDecimal newInstance(Ruby runtime, IRubyObject recv, RubyBigDecimal arg) {
return new RubyBigDecimal(runtime, (RubyClass) recv, arg);
}

@JRubyMethod(name = "new", required = 1, optional = 1, meta = true)
public static RubyBigDecimal newInstance(IRubyObject recv, IRubyObject[] args) {
BigDecimal decimal;
Ruby runtime = recv.getRuntime();
ThreadContext ctx = runtime.getCurrentContext();
private static RubyBigDecimal newInstance(Ruby runtime, IRubyObject recv, RubyFixnum arg, MathContext mathContext) {
return new RubyBigDecimal(runtime, (RubyClass) recv, new BigDecimal(arg.getLongValue(), mathContext));
}

if (args.length == 0) {
decimal = new BigDecimal(0);
} else {
MathContext context = MathContext.UNLIMITED;
private static RubyBigDecimal newInstance(ThreadContext context, RubyRational arg, MathContext mathContext) {
BigDecimal num = new BigDecimal(arg.numerator(context).convertToInteger().getLongValue());
BigDecimal den = new BigDecimal(arg.denominator(context).convertToInteger().getLongValue());
BigDecimal value = num.divide(den, mathContext);

if (args.length == 2) {
int digits = (int)args[1].convertToInteger().getLongValue();
if (digits < 0) {
throw runtime.newArgumentError("argument must be positive");
}
context = new MathContext(digits);
}
return new RubyBigDecimal(context.runtime, value);
}

if (runtime.is1_9()) {
if (args[0] instanceof RubyBigDecimal) {
return new RubyBigDecimal(runtime, (RubyClass)recv, ((RubyBigDecimal)args[0]));
} else if (args[0] instanceof RubyFloat || args[0] instanceof RubyRational) {
if (args.length != 2) {
// float input must be accompanied by precision
throw runtime.newArgumentError("can't omit precision for a rational");
}
private static RubyBigDecimal newInstance(Ruby runtime, IRubyObject recv, RubyFloat arg, MathContext mathContext) {
// precision can be no more than float digits
if (mathContext.getPrecision() > RubyFloat.DIG + 1) throw runtime.newArgumentError("precision too large");

if (args[0] instanceof RubyFloat) {
// precision can be no more than float digits
if (context.getPrecision() > RubyFloat.DIG + 1) {
throw runtime.newArgumentError("precision too large");
}
return new RubyBigDecimal(runtime, (RubyClass)recv, new BigDecimal(((RubyFloat)args[0]).getDoubleValue(), context));
} else {
RubyRational rat = (RubyRational)args[0];
return new RubyBigDecimal(runtime, (RubyClass) recv, new BigDecimal(arg.getDoubleValue(), mathContext));
}

BigDecimal num = new BigDecimal(rat.numerator(ctx).convertToInteger().getLongValue());
BigDecimal den = new BigDecimal(rat.denominator(ctx).convertToInteger().getLongValue());
private static RubyBigDecimal newInstance(Ruby runtime, IRubyObject recv, RubyBignum arg, MathContext mathContext) {
return new RubyBigDecimal(runtime, (RubyClass) recv, new BigDecimal(arg.getBigIntegerValue(), mathContext));
}

BigDecimal value = num.divide(den, context);
private final static Pattern NUMBER_PATTERN = Pattern.compile("^([+-]?\\d*\\.?\\d*([eE][+-]?)?\\d*).*");

return new RubyBigDecimal(runtime, value);
}
} else if (args[0] instanceof RubyFixnum) {
return new RubyBigDecimal(runtime, (RubyClass)recv, new BigDecimal(((RubyFixnum)args[0]).getLongValue(), context));
} else if (args[0] instanceof RubyBignum) {
return new RubyBigDecimal(runtime, (RubyClass)recv, new BigDecimal(((RubyBignum)args[0]).getBigIntegerValue(), context));
} else if (!runtime.is1_8()) {
context = MathContext.UNLIMITED;
}
// fall through to String coercion below
}
private static RubyBigDecimal newInstance(ThreadContext context, IRubyObject recv, IRubyObject arg, MathContext mathContext) {
String strValue = arg.convertToString().toString().trim();

String strValue = args[0].convertToString().toString();
strValue = strValue.trim();
if ("NaN".equals(strValue)) {
return newNaN(runtime);
}
switch ( strValue.length() > 2 ? strValue.charAt(0) : ' ' ) { // do not case length == 1
case 'N' :
if ( "NaN".equals(strValue) ) return newNaN(context.runtime);
case 'I' :
if ( "Infinity".equals(strValue) ) return newInfinity(context.runtime, 1);
case '-' :
if ( "-Infinity".equals(strValue) ) return newInfinity(context.runtime, -1);
case '+' :
if ( "+Infinity".equals(strValue) ) return newInfinity(context.runtime, +1);
}

Matcher m = INFINITY_PATTERN.matcher(strValue);
if (m.matches()) {
int sign = 1;
String signGroup = m.group(1);
if ("-".equals(signGroup)) {
sign = -1;
}
return newInfinity(runtime, sign);
}
// Convert String to Java understandable format (for BigDecimal).
strValue = strValue.replaceFirst("[dD]", "E"); // 1. MRI allows d and D as exponent separators
strValue = strValue.replaceAll("_", ""); // 2. MRI allows underscores anywhere
strValue = NUMBER_PATTERN.matcher(strValue).replaceFirst("$1"); // 3. MRI ignores the trailing junk

// Clean-up string representation so that it could be understood
// by Java's BigDecimal. Not terribly efficient for now.
// 1. MRI allows d and D as exponent separators
strValue = strValue.replaceFirst("[dD]", "E");
// 2. MRI allows underscores anywhere
strValue = strValue.replaceAll("_", "");
// 3. MRI ignores the trailing junk
strValue = NUMBER_PATTERN.matcher(strValue).replaceFirst("$1");
BigDecimal decimal;
try {
decimal = new BigDecimal(strValue, mathContext);
}
catch (NumberFormatException e) {
if (isOverflowExceptionMode(context.runtime)) throw context.runtime.newFloatDomainError("exponent overflow");

try {
decimal = new BigDecimal(strValue, context);
} catch(NumberFormatException e) {
if (isOverflowExceptionMode(runtime)) {
throw runtime.newFloatDomainError("exponent overflow");
}
decimal = new BigDecimal(0);
}
if (decimal.signum() == 0) {
// MRI behavior: -0 and +0 are two different things
if (strValue.matches("^\\s*-.*")) {
return newZero(runtime, -1);
} else {
return newZero(runtime, 1);
}
}
decimal = new BigDecimal(0);
}
return new RubyBigDecimal(runtime, (RubyClass)recv, decimal);

// MRI behavior: -0 and +0 are two different things
if (decimal.signum() == 0) return newZero(context.runtime, strValue.matches("^\\s*-.*") ? -1 : 1);

return new RubyBigDecimal(context.runtime, (RubyClass) recv, decimal);
}

private static RubyBigDecimal newZero(Ruby runtime, int sign) {
int zeroSign;
if (sign < 0) {
zeroSign = -1;
} else {
zeroSign = 1;
@Deprecated
public static RubyBigDecimal newInstance(IRubyObject recv, IRubyObject[] args) {
final ThreadContext context = recv.getRuntime().getCurrentContext();
switch (args.length) {
case 1: return newInstance(context, recv, args[0]);
case 2: return newInstance(context, recv, args[0], args[1]);
}
RubyBigDecimal rbd = new RubyBigDecimal(runtime, BigDecimal.ZERO, 0, zeroSign);
return rbd;
throw new IllegalArgumentException("unexpected argument count: " + args.length);
}

private static RubyBigDecimal newNaN(Ruby runtime) {
if (isNaNExceptionMode(runtime)) throw runtime.newFloatDomainError("Computation results to 'NaN'(Not a Number)");
@JRubyMethod(name = "new", meta = true)
public static RubyBigDecimal newInstance(ThreadContext context, IRubyObject recv, IRubyObject arg) {
if ( context.runtime.is1_9() ) {
if (arg instanceof RubyBigDecimal) return newInstance(context.runtime, recv, (RubyBigDecimal) arg);
if (arg instanceof RubyFloat || arg instanceof RubyRational) throw context.runtime.newArgumentError("can't omit precision for a rational");
if (arg instanceof RubyFixnum) return newInstance(context.runtime, recv, (RubyFixnum) arg, MathContext.UNLIMITED);
if (arg instanceof RubyBignum) return newInstance(context.runtime, recv, (RubyBignum) arg, MathContext.UNLIMITED);
}

RubyBigDecimal rbd = new RubyBigDecimal(runtime, BigDecimal.ZERO, true);
return rbd;
return newInstance(context, recv, arg, MathContext.UNLIMITED);
}

private static RubyBigDecimal newInfinity(Ruby runtime, int sign) {
int infinitySign;
if (sign < 0) {
infinitySign = -1;
} else {
infinitySign = 1;
@JRubyMethod(name = "new", meta = true)
public static RubyBigDecimal newInstance(ThreadContext context, IRubyObject recv, IRubyObject arg, IRubyObject mathArg) {
int digits = (int) mathArg.convertToInteger().getLongValue();
if (digits < 0) throw context.runtime.newArgumentError("argument must be positive");

MathContext mathContext = new MathContext(digits);

if ( context.runtime.is1_9() ) {
if (arg instanceof RubyBigDecimal) return newInstance(context.runtime, recv, (RubyBigDecimal) arg);
if (arg instanceof RubyFloat) return newInstance(context.runtime, recv, (RubyFloat) arg, mathContext);
if (arg instanceof RubyRational) return newInstance(context, (RubyRational) arg, mathContext);
if (arg instanceof RubyFixnum) return newInstance(context.runtime, recv, (RubyFixnum) arg, mathContext);
if (arg instanceof RubyBignum) return newInstance(context.runtime, recv, (RubyBignum) arg, mathContext);
mathContext = MathContext.UNLIMITED;
}
RubyBigDecimal rbd = new RubyBigDecimal(runtime, BigDecimal.ZERO, infinitySign);
if (isInfinityExceptionMode(runtime)) throw runtime.newFloatDomainError("Computation results to 'Infinity'");

return rbd;
return newInstance(context, recv, arg, mathContext);
}

private static RubyBigDecimal newZero(final Ruby runtime, final int sign) {
return new RubyBigDecimal(runtime, BigDecimal.ZERO, 0, sign < 0 ? -1 : 1);
}

private static RubyBigDecimal newNaN(Ruby runtime) {
if ( isNaNExceptionMode(runtime) ) {
throw runtime.newFloatDomainError("Computation results to 'NaN'(Not a Number)");
}
return new RubyBigDecimal(runtime, BigDecimal.ZERO, true);
}

private static RubyBigDecimal newInfinity(final Ruby runtime, final int sign) {
if ( isInfinityExceptionMode(runtime) ) {
throw runtime.newFloatDomainError("Computation results to 'Infinity'");
}
return new RubyBigDecimal(runtime, BigDecimal.ZERO, sign < 0 ? -1 : 1);
}

private RubyBigDecimal setResult() {
51 changes: 49 additions & 2 deletions test/test_big_decimal.rb
Original file line number Diff line number Diff line change
@@ -309,6 +309,25 @@ def test_large_bigdecimal_to_f
assert BigDecimal.new("5E-69999999").to_f < Float::EPSILON
end

def test_infinity
assert_equal true, BigDecimal.new("0.0000000001").finite?

#if RUBY_VERSION > '1.9'
# assert_raises(FloatDomainError) { BigDecimal("Infinity") }
# assert_raises(FloatDomainError) { BigDecimal("+Infinity") }
# assert_raises(FloatDomainError) { BigDecimal("-Infinity") }
#else
assert_equal 1, BigDecimal("Infinity").infinite?
assert_equal false, BigDecimal("-Infinity").finite?
assert_equal false, BigDecimal("+Infinity").finite?
#end

assert_raises(TypeError) { BigDecimal(:"+Infinity") }

assert_equal BigDecimal('0'), BigDecimal("infinity")
assert_equal BigDecimal('0'), BigDecimal("+Infinit")
end

#JRUBY-5190
def test_large_precisions
a = BigDecimal("1").div(BigDecimal("3"), 307)
@@ -319,12 +338,12 @@ def test_large_precisions
if RUBY_VERSION =~ /1\.9/ || RUBY_VERSION =~ /2\.0/
# GH-644, GH-648
def test_div_by_float_precision_gh644
a = BigDecimal.new(11023)/2.2046
a = BigDecimal.new(11023) / 2.2046
assert_equal 5_000, a.to_f
end

def test_div_by_float_precision_gh648
b = BigDecimal.new(1.05, 10)/1.48
b = BigDecimal.new(1.05, 10) / 1.48
assert (b.to_f - 0.7094594594594595) < Float::EPSILON
end

@@ -333,4 +352,32 @@ def test_GH_2650
assert_equal(BigDecimal.new("10.9", 2).to_f, 10.9)
end
end

class BigDeci < BigDecimal

# MRI does not invoke initialize on 1.8./1.9
def initialize(arg); raise super(arg.to_s) end

def abs; -super end
def infinite?; false end

end

def test_subclass
a = BigDeci.new 1.to_s
assert_equal -1, a.abs
assert_equal false, a.infinite?

a = BigDeci.new '-100'
assert_equal -5, a.div(20)
assert_equal -100, a.abs

assert a.inspect.index('#<BigDecimal:')
assert_equal '-0.1E3', a.to_s

assert_equal BigDeci, a.class
assert a.is_a?(BigDeci)
assert a.kind_of?(BigDeci)
end

end