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

Commits on Jun 4, 2015

  1. Copy the full SHA
    fb4d26b View commit details
  2. Copy the full SHA
    46ae07b View commit details
  3. Copy the full SHA
    7daf537 View commit details
  4. handle big decimal ** float value calculation (with Java double math)…

    … ...
    
    since we're now not raising users should get ~ the value they expect (closing #1967)
    kares committed Jun 4, 2015
    Copy the full SHA
    5c5b5cb View commit details
Showing with 179 additions and 190 deletions.
  1. +144 −189 core/src/main/java/org/jruby/ext/bigdecimal/RubyBigDecimal.java
  2. +35 −1 test/test_big_decimal.rb
333 changes: 144 additions & 189 deletions core/src/main/java/org/jruby/ext/bigdecimal/RubyBigDecimal.java
Original file line number Diff line number Diff line change
@@ -241,9 +241,9 @@ public IRubyObject dump(IRubyObject[] args, Block unusedBlock) {

@JRubyMethod(name = "_load", required = 1, meta = true)
public static RubyBigDecimal load(IRubyObject recv, IRubyObject from, Block block) {
RubyBigDecimal rubyBigDecimal = (RubyBigDecimal) (((RubyClass)recv).allocate());
RubyBigDecimal rubyBigDecimal = (RubyBigDecimal) (((RubyClass) recv).allocate());
String precisionAndValue = from.convertToString().asJavaString();
String value = precisionAndValue.substring(precisionAndValue.indexOf(":")+1);
String value = precisionAndValue.substring(precisionAndValue.indexOf(':') + 1);
rubyBigDecimal.value = new BigDecimal(value);
return rubyBigDecimal;
}
@@ -254,70 +254,51 @@ public static IRubyObject double_fig(IRubyObject recv) {
}

@JRubyMethod(name = "limit", optional = 1, meta = true)
public static IRubyObject limit(IRubyObject recv, IRubyObject[] args) {
Ruby runtime = recv.getRuntime();
RubyModule c = (RubyModule)recv;
IRubyObject nCur = c.searchInternalModuleVariable("vpPrecLimit");
public static IRubyObject limit(final IRubyObject self, IRubyObject[] args) {
final RubyModule BigDecimal = (RubyModule) self;
IRubyObject current = BigDecimal.searchInternalModuleVariable("vpPrecLimit");

if (args.length > 0) {
IRubyObject arg = args[0];
if (!arg.isNil()) {
final Ruby runtime = self.getRuntime();
if (!(arg instanceof RubyFixnum)) {
throw runtime.newTypeError(arg, runtime.getFixnum());
}
if (0 > ((RubyFixnum)arg).getLongValue()) {
throw runtime.newArgumentError("argument must be positive");
}
c.setInternalModuleVariable("vpPrecLimit", arg);
BigDecimal.setInternalModuleVariable("vpPrecLimit", arg);
}
}

return nCur;
return current;
}

@JRubyMethod(name = "save_limit", meta = true)
public static IRubyObject save_limit(ThreadContext context, IRubyObject recv, Block block) {
RubyModule c = (RubyModule)recv;
IRubyObject nCur = c.searchInternalModuleVariable("vpPrecLimit");
IRubyObject ret;

try {
ret = block.yieldSpecific(context);
} finally {
c.setInternalModuleVariable("vpPrecLimit", nCur);
}

return ret;
public static IRubyObject save_limit(ThreadContext context, IRubyObject self, Block block) {
return saveVariable(context, (RubyModule) self, block, "vpPrecLimit");
}

@JRubyMethod(name = "save_exception_mode", meta = true)
public static IRubyObject save_exception_mode(ThreadContext context, IRubyObject recv, Block block) {
RubyModule c = (RubyModule)recv;
IRubyObject nCur = c.searchInternalModuleVariable("vpExceptionMode");
IRubyObject ret;

try {
ret = block.yieldSpecific(context);
} finally {
c.setInternalModuleVariable("vpExceptionMode", nCur);
}

return ret;
public static IRubyObject save_exception_mode(ThreadContext context, IRubyObject self, Block block) {
return saveVariable(context, (RubyModule) self, block, "vpExceptionMode");
}

@JRubyMethod(name = "save_rounding_mode", meta = true)
public static IRubyObject save_rounding_mode(ThreadContext context, IRubyObject recv, Block block) {
RubyModule c = (RubyModule)recv;
IRubyObject nCur = c.searchInternalModuleVariable("vpRoundingMode");
IRubyObject ret;
public static IRubyObject save_rounding_mode(ThreadContext context, IRubyObject self, Block block) {
return saveVariable(context, (RubyModule) self, block, "vpRoundingMode");
}

private static IRubyObject saveVariable(final ThreadContext context, final RubyModule BigDecimal,
final Block block, final String intVariableName) {
IRubyObject current = BigDecimal.searchInternalModuleVariable(intVariableName);
try {
ret = block.yieldSpecific(context);
} finally {
c.setInternalModuleVariable("vpRoundingMode", nCur);
return block.yieldSpecific(context);
}
finally {
BigDecimal.setInternalModuleVariable(intVariableName, current);
}

return ret;
}

@JRubyMethod(name = "mode", required = 1, optional = 1, meta = true)
@@ -809,111 +790,115 @@ private RubyBigDecimal multInternal(final Ruby runtime, RubyBigDecimal val, IRub
return new RubyBigDecimal(runtime, res).setResult();
}

@JRubyMethod(name = {"**", "power"}, required = 1, compat = CompatVersion.RUBY1_8)
// @Deprecated
public IRubyObject op_pow(IRubyObject arg) {
if (!(arg instanceof RubyFixnum)) {
throw getRuntime().newTypeError("wrong argument type " + arg.getMetaClass() + " (expected Fixnum)");
}
return op_pow(getRuntime().getCurrentContext(), arg);
}

if (isNaN() || isInfinity()) {
return newNaN(getRuntime());
@JRubyMethod(name = {"**", "power"}, required = 1, compat = CompatVersion.RUBY1_8)
public RubyBigDecimal op_pow(final ThreadContext context, IRubyObject arg) {
if ( ! (arg instanceof RubyFixnum) ) {
throw context.runtime.newTypeError("wrong argument type " + arg.getMetaClass() + " (expected Fixnum)");
}

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

int times = RubyNumeric.fix2int(arg.convertToInteger());

if (times < 0) {
if (isZero()) {
return newInfinity(getRuntime(), value.signum());
}

// 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 new RubyBigDecimal(getRuntime(),
value.pow(times, new MathContext(precision, RoundingMode.HALF_UP)));
} else {
return new RubyBigDecimal(getRuntime(), value.pow(times));
if (isZero()) return newInfinity(context.runtime, value.signum());
return new RubyBigDecimal(context.runtime, powNegative(times));
}
return new RubyBigDecimal(context.runtime, value.pow(times));
}

@JRubyMethod(name = {"**", "power"}, required = 1, compat = CompatVersion.RUBY1_9)
// @Deprecated
public IRubyObject op_pow19(IRubyObject exp) {
if (!(exp instanceof RubyFixnum)) {
throw getRuntime().newTypeError("wrong argument type " + exp.getMetaClass() + " (expected Fixnum)");
}
Ruby runtime = getRuntime();
ThreadContext context = runtime.getCurrentContext();
return op_pow19(getRuntime().getCurrentContext(), exp);
}

if (isNaN()) {
return newNaN(runtime);
@JRubyMethod(name = {"**", "power"}, required = 1, compat = CompatVersion.RUBY1_9)
public RubyBigDecimal op_pow19(ThreadContext context, IRubyObject exp) {
final Ruby runtime = context.runtime;

if ( ! (exp instanceof RubyNumeric) ) {
throw context.runtime.newTypeError("wrong argument type " + exp.getMetaClass() + " (expected scalar Numeric)");
}

if (isNaN()) return newNaN(runtime);

if (isInfinity()) {
if (Numeric.f_negative_p(context, exp)) {
if (infinitySign < 0) {
if (Numeric.f_integer_p(context, exp).isTrue()) {
if (is_even(exp)) {
/* (-Infinity) ** (-even_integer) -> +0 */
return newZero(runtime, 1);
} else {
/* (-Infinity) ** (-odd_integer) -> -0 */
return newZero(runtime, -1);
}
} else {
/* (-Infinity) ** (-non_integer) -> -0 */
return newZero(runtime, -1);
if ( Numeric.f_integer_p(context, exp).isTrue() ) {
/* (-Infinity) ** (-even_integer) -> +0 */
/* (-Infinity) ** (-odd_integer) -> -0 */
return newZero(runtime, is_even(exp) ? +1 : -1);
}
} else {
/* (-Infinity) ** (-non_integer) -> -0 */
return newZero(runtime, -1);
}
else {
return newZero(runtime, 0);

}
} else {
if (infinitySign < 0) {
if (Numeric.f_integer_p(context, exp).isTrue()) {
if (is_even(exp)) {
return newInfinity(runtime, 1);
} else {
return newInfinity(runtime, -1);
}
} else {
throw runtime.newMathDomainError("a non-integral exponent for a negative base");
if ( Numeric.f_integer_p(context, exp).isTrue() ) {
return newInfinity(runtime, is_even(exp) ? +1 : -1);
}
} else {
return newInfinity(runtime, 1);
throw runtime.newMathDomainError("a non-integral exponent for a negative base");
}
return newInfinity(runtime, 1);
}
}

int times = RubyNumeric.fix2int(exp.convertToInteger());

if (times < 0) {
if (isZero()) {
return newInfinity(getRuntime(), value.signum());
}
final int times; final double rem; // exp's decimal part
// when pow is not an integer we're play the oldest trick :
// X pow (T+R) = X pow T * X pow R
if ( ! ( exp instanceof RubyInteger ) ) {
BigDecimal expVal = BigDecimal.valueOf( ((RubyNumeric) exp).getDoubleValue() );
BigDecimal[] divAndRem = expVal.divideAndRemainder(BigDecimal.ONE);
times = divAndRem[0].intValueExact(); rem = divAndRem[1].doubleValue();
}
else {
times = RubyNumeric.fix2int(exp); rem = 0;
}

// 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);
BigDecimal pow;
if ( times < 0 ) {
if (isZero()) return newInfinity(context.runtime, value.signum());
pow = powNegative(times);
}
else {
pow = value.pow(times);
}

return new RubyBigDecimal(getRuntime(),
value.pow(times, new MathContext(precision, RoundingMode.HALF_UP)));
} else {
return new RubyBigDecimal(getRuntime(), value.pow(times));
if ( rem > 0 ) {
// TODO of course this assumes we fit into double (and we loose some precision)
double remPow = Math.pow(value.doubleValue(), rem);
pow = pow.multiply( BigDecimal.valueOf(remPow) );
}

return new RubyBigDecimal(runtime, pow);
}

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 = "+", compat = CompatVersion.RUBY1_8)
public IRubyObject op_plus(ThreadContext context, IRubyObject b) {
RubyBigDecimal val = getVpValue(context, b, false);
return addInternal(context, val, b, getRuntime().getClass("BigDecimal")
.searchInternalModuleVariable("vpPrecLimit"));
return addInternal(context, val, b, vpPrecLimit(context.runtime));
}

@JRubyMethod(name = "+", compat = CompatVersion.RUBY1_9)
public IRubyObject op_plus19(ThreadContext context, IRubyObject b) {
RubyBigDecimal val = getVpValue19(context, b, false);
return addInternal(context, val, b, getRuntime().getClass("BigDecimal")
.searchInternalModuleVariable("vpPrecLimit"));
return addInternal(context, val, b, vpPrecLimit(context.runtime));
}

@JRubyMethod(name = "add", compat = CompatVersion.RUBY1_8)
@@ -942,45 +927,43 @@ private IRubyObject addInternal(ThreadContext context, RubyBigDecimal val, IRuby
return callCoerced(context, "+", b, true);
}

RubyBigDecimal res = handleAddSpecialValues(val);
if (res != null) {
return res;
}
RubyBigDecimal res = handleAddSpecialValues(context, val);
if ( res != null ) return res;

RoundingMode roundMode = getRoundingMode(runtime);
return new RubyBigDecimal(runtime, value.add(
val.value, new MathContext(prec, roundMode))); // TODO: why this: .setResult();
}

private int getPositiveInt(ThreadContext context, IRubyObject arg) {
Ruby runtime = context.runtime;
private static int getPositiveInt(ThreadContext context, IRubyObject arg) {
final Ruby runtime = context.runtime;

if (arg instanceof RubyFixnum) {
if ( arg instanceof RubyFixnum ) {
int value = RubyNumeric.fix2int(arg);
if (value < 0) {
throw runtime.newArgumentError("argument must be positive");
}
return value;
} else {
throw runtime.newTypeError(arg, runtime.getFixnum());
}
throw runtime.newTypeError(arg, runtime.getFixnum());
}

private RubyBigDecimal handleAddSpecialValues(RubyBigDecimal val) {
private RubyBigDecimal handleAddSpecialValues(ThreadContext context, RubyBigDecimal val) {
if (isNaN() || val.isNaN) {
return newNaN(getRuntime());
return newNaN(context.runtime);
}

int sign = infinitySign * val.infinitySign;
if (sign > 0) {
return isInfinity() ? this : val;
}
if (sign < 0) {
return newNaN(getRuntime());
return newNaN(context.runtime);
}
if (sign == 0) {
sign = infinitySign + val.infinitySign;
if (sign != 0) {
return newInfinity(getRuntime(), sign);
return newInfinity(context.runtime, sign);
}
}
return null;
@@ -1019,21 +1002,21 @@ private IRubyObject subInternal(ThreadContext context, RubyBigDecimal val, IRuby
if (val == null) {
return callCoerced(context, "-", b);
}
RubyBigDecimal res = handleMinusSpecialValues(val);
RubyBigDecimal res = handleMinusSpecialValues(context, val);
if (res != null) {
return res;
}
return new RubyBigDecimal(getRuntime(), value.subtract(val.value)).setResult();
return new RubyBigDecimal(context.runtime, value.subtract(val.value)).setResult();
}

private RubyBigDecimal handleMinusSpecialValues(RubyBigDecimal val) {
private RubyBigDecimal handleMinusSpecialValues(ThreadContext context, RubyBigDecimal val) {
if (isNaN() || val.isNaN()) {
return newNaN(getRuntime());
return newNaN(context.runtime);
}

int sign = infinitySign * val.infinitySign;
if (sign > 0) {
return newNaN(getRuntime());
return newNaN(context.runtime);
}
if (sign < 0) {
return this;
@@ -1043,19 +1026,19 @@ private RubyBigDecimal handleMinusSpecialValues(RubyBigDecimal val) {
return this;
}
if (val.isInfinity()) {
return newInfinity(getRuntime(), val.infinitySign * -1);
return newInfinity(context.runtime, val.infinitySign * -1);
}
sign = infinitySign + val.infinitySign;
if (sign != 0) {
return newInfinity(getRuntime(), sign);
return newInfinity(context.runtime, sign);
}
}
return null;
}

@JRubyMethod(name = "-@")
public IRubyObject op_uminus() {
Ruby runtime = getRuntime();
final Ruby runtime = getRuntime();
if (isNaN()) {
return newNaN(runtime);
}
@@ -1065,7 +1048,7 @@ public IRubyObject op_uminus() {
if (isZero()) {
return newZero(runtime, -zeroSign);
}
return new RubyBigDecimal(getRuntime(), value.negate());
return new RubyBigDecimal(runtime, value.negate());
}

@JRubyMethod(name = {"/", "quo"}, compat = CompatVersion.RUBY1_8)
@@ -1825,47 +1808,30 @@ public static int formatFractionalDigitGroups(String format) {
return groups;
}

private boolean hasArg(IRubyObject[] args) {
return args.length != 0 && !args[0].isNil();
private static String firstArgument(IRubyObject[] args) {
if ( args.length == 0 ) return null;
final IRubyObject arg = args[0];
return arg.isNil() ? null : arg.toString();
}

private String format(IRubyObject[] args) {
return args[0].toString();
private static boolean posSpace(String arg) {
if ( arg == null ) return false;
return formatHasLeadingSpace(arg);
}

private String firstArgument(IRubyObject[] args) {
if (hasArg(args)) {
return format(args);
}
return null;
}

private boolean posSpace(String arg) {
if (null != arg) {
return formatHasLeadingSpace(arg);
}
return false;
private static boolean posSign(String arg) {
if ( arg == null ) return false;
return formatHasLeadingPlus(arg) || posSpace(arg);
}

private boolean posSign(String arg) {
if (null != arg) {
return formatHasLeadingPlus(arg) || posSpace(arg);
}
return false;
}

private boolean asEngineering(String arg) {
if (null != arg) {
return !formatHasFloatingPointNotation(arg);
}
return true;
private static boolean asEngineering(String arg) {
if ( arg == null ) return true;
return ! formatHasFloatingPointNotation(arg);
}

private int groups(String arg) {
if (null != arg) {
return formatFractionalDigitGroups(arg);
}
return 0;
private static int groups(String arg) {
if (arg == null) return 0;
return formatFractionalDigitGroups(arg);
}

private boolean isZero() {
@@ -1892,8 +1858,9 @@ private String sign(String arg, int signum) {
return signum == -1 ? "-" : (signum == 1 ? (posSign(arg) ? (posSpace(arg) ? " " : "+") : "") : "");
}

private IRubyObject engineeringValue(String arg) {
StringBuilder build = new StringBuilder().append(sign(arg, value.signum())).append("0.");
private CharSequence engineeringValue(String arg) {
StringBuilder build = new StringBuilder();
build.append( sign(arg, value.signum()) ).append("0.");
String s = removeTrailingZeroes(unscaledValue());

if (groups(arg) == 0) {
@@ -1908,11 +1875,11 @@ private IRubyObject engineeringValue(String arg) {
sep = " ";
}
}
build.append("E").append(getExponent());
return getRuntime().newString(build.toString());
build.append('E').append(getExponent());
return build;
}

private IRubyObject floatingPointValue(String arg) {
private CharSequence floatingPointValue(String arg) {
String values[] = value.abs().stripTrailingZeros().toPlainString().split("\\.");
String whole = "0";
if (values.length > 0) {
@@ -1957,34 +1924,22 @@ private IRubyObject floatingPointValue(String arg) {
}
}
}
return getRuntime().newString(build.toString());
return build;
}

@JRubyMethod(name = "to_s", optional = 1)
public IRubyObject to_s(IRubyObject[] args) {
public RubyString to_s(IRubyObject[] args) {
String arg = firstArgument(args);
if (isNaN()) {
return getRuntime().newString("NaN");
}
if (infinitySign != 0) {
if (infinitySign == -1) {
return getRuntime().newString("-Infinity");
} else {
return getRuntime().newString("Infinity");
}
}
if (isZero()) {
String zero = "0.0";
if (zeroSign < 0) {
zero = "-" + zero;
}
return getRuntime().newString(zero);
if ( isNaN() ) return getRuntime().newString("NaN");
if ( infinitySign != 0 ) {
return getRuntime().newString(infinitySign == -1 ? "-Infinity" : "Infinity");
}
if (asEngineering(arg)) {
return engineeringValue(arg);
} else {
return floatingPointValue(arg);
if ( isZero() ) {
return getRuntime().newString(zeroSign < 0 ? "-0.0" : "0.0");
}
return getRuntime().newString(
( asEngineering(arg) ? engineeringValue(arg) : floatingPointValue(arg) ).toString()
);
}

// Note: #fix has only no-arg form, but truncate allows optional parameter.
36 changes: 35 additions & 1 deletion test/test_big_decimal.rb
Original file line number Diff line number Diff line change
@@ -193,12 +193,46 @@ def test_math_extension
end

def test_big_decimal_power
require 'bigdecimal/math'

n = BigDecimal("10")
assert_equal(n.power(0), BigDecimal("1"))
assert_equal(n.power(1), n)
assert_equal(n.power(2), BigDecimal("100"))
assert_equal(n.power(-1), BigDecimal("0.1"))
assert_raises(TypeError) { n.power(1.1) }

if RUBY_VERSION < '1.9'
assert_raises(TypeError) { n.power(1.1) }
assert_raises(TypeError) { n.power('1') }
else
n.power(1.1)

begin
n.power('1.1')
rescue TypeError => e
assert_equal 'wrong argument type String (expected scalar Numeric)', e.message
else
fail 'expected to raise TypeError'
end

assert_equal BigDecimal('0.1E2'), n.power(1.0)

res = n.power(1.1)
#assert_equal BigDecimal('0.125892541E2'), res
# NOTE: we're not handling precision the same as MRI with pow
assert_equal '0.125892541', res.to_s[0..10]
assert_equal 'E2', res.to_s[-2..-1]

res = 2 ** BigDecimal(1.2, 2)
#assert_equal BigDecimal('0.229739671E1'), res
# NOTE: we're not handling precision the same as MRI with pow
assert_equal '0.22973967', res.to_s[0..9]
assert_equal 'E1', res.to_s[-2..-1]

res = BigDecimal(1.2, 2) ** 2.0
assert_equal BigDecimal('0.144E1'), res
end

end

def test_big_decimal_mode