Skip to content

Commit d80171f

Browse files
committed
Improve BigDecimal compatibility
1 parent c2724f0 commit d80171f

File tree

22 files changed

+289
-62
lines changed

22 files changed

+289
-62
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Compatibility:
1818
* `blocking: true` is now supported for `FFI::Library#attach_function`.
1919
* Implemented `Proc#>>` and `#<<` (#1688).
2020
* `Thread.report_on_exception` is now `true` by default like MRI 2.5+.
21+
* `BigDecimal` compatibility has been generally improved in several ways.
2122

2223
Changes:
2324

lib/truffle/bigdecimal.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,4 +331,20 @@ def BigDecimal(value, precision = Truffle::UNDEFINED)
331331
end
332332
end
333333

334+
module Truffle::BigDecimalOperations
335+
336+
def self.coerce_integer_to_bigdecimal(value)
337+
Truffle.invoke_primitive(:bigdecimal_new, value, Truffle::UNDEFINED, true)
338+
end
339+
340+
def self.coerce_float_to_bigdecimal(value)
341+
Truffle.invoke_primitive(:bigdecimal_new, value.to_s, Truffle::UNDEFINED, true)
342+
end
343+
344+
def self.coerce_rational_to_bigdecimal(value)
345+
Truffle.invoke_primitive(:bigdecimal_new, value.to_f.to_s, Truffle::UNDEFINED, true)
346+
end
347+
348+
end
349+
334350
require 'bigdecimal/math'

spec/ruby/core/integer/coerce_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
require_relative '../../spec_helper'
22

3+
require 'bigdecimal'
4+
35
describe "Integer#coerce" do
46
context "fixnum" do
57
describe "when given a Fixnum" do
@@ -88,4 +90,15 @@
8890
ary.should == [1.2, a.to_f]
8991
end
9092
end
93+
94+
context "bigdecimal" do
95+
it "produces Floats" do
96+
x, y = 3.coerce(BigDecimal("3.4"))
97+
x.class.should == Float
98+
x.should == 3.4
99+
y.class.should == Float
100+
y.should == 3.0
101+
end
102+
end
103+
91104
end

spec/ruby/library/bigdecimal/add_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@
8181
end
8282
end
8383

84+
describe "with Rational" do
85+
it "produces a BigDecimal" do
86+
(@three + Rational(500, 2)).should == BigDecimal("0.253e3")
87+
end
88+
end
89+
8490
it "favors the precision specified in the second argument over the global limit" do
8591
BigDecimalSpecs.with_limit(1) do
8692
BigDecimal('0.888').add(@zero, 3).should == BigDecimal('0.888')

spec/ruby/library/bigdecimal/divide_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,14 @@
44

55
describe "BigDecimal#/" do
66
it_behaves_like :bigdecimal_quo, :/, []
7+
8+
before :each do
9+
@three = BigDecimal("3")
10+
end
11+
12+
describe "with Rational" do
13+
it "produces a BigDecimal" do
14+
(@three / Rational(500, 2)).should == BigDecimal("0.12e-1")
15+
end
16+
end
717
end

spec/ruby/library/bigdecimal/multiply_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
describe "BigDecimal#*" do
1010
before :each do
11+
@three = BigDecimal("3")
1112
@e3_minus = BigDecimal("3E-20001")
1213
@e3_plus = BigDecimal("3E20001")
1314
@e = BigDecimal("1.00000000000000000000123456789")
@@ -31,4 +32,10 @@
3132
(@e3_minus * object).should == BigDecimal("9")
3233
end
3334
end
35+
36+
describe "with Rational" do
37+
it "produces a BigDecimal" do
38+
(@three * Rational(500, 2)).should == BigDecimal("0.75e3")
39+
end
40+
end
3441
end

spec/ruby/library/bigdecimal/sub_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@one = BigDecimal("1")
88
@zero = BigDecimal("0")
99
@two = BigDecimal("2")
10+
@three = BigDecimal("3")
1011
@nan = BigDecimal("NaN")
1112
@infinity = BigDecimal("Infinity")
1213
@infinity_minus = BigDecimal("-Infinity")
@@ -42,6 +43,12 @@
4243
end
4344
end
4445

46+
describe "with Rational" do
47+
it "produces a BigDecimal" do
48+
(@three - Rational(500, 2)).should == BigDecimal('-0.247e3')
49+
end
50+
end
51+
4552
it "returns NaN if NaN is involved" do
4653
@one.sub(@nan, 1).nan?.should == true
4754
@nan.sub(@one, 1).nan?.should == true

spec/ruby/shared/rational/coerce.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
require_relative '../../spec_helper'
22

3+
require 'bigdecimal'
4+
35
describe :rational_coerce, shared: true do
46
it "returns the passed argument, self as Float, when given a Float" do
57
result = Rational(3, 4).coerce(1.0)
@@ -18,4 +20,10 @@
1820
it "returns [argument, self] when given a Rational" do
1921
Rational(3, 7).coerce(Rational(9, 2)).should == [Rational(9, 2), Rational(3, 7)]
2022
end
23+
24+
it "raises an error when passed a BigDecimal" do
25+
-> {
26+
Rational(500, 3).coerce(BigDecimal('166.666666666'))
27+
}.should raise_error(TypeError, /BigDecimal can't be coerced into Rational/)
28+
end
2129
end

spec/tags/truffle/bigdecimal_tags.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fails:BigDecimal bug GR-16506 produces the expected result

spec/truffle/bigdecimal_spec.rb

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved. This
2+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
3+
# redistribute it and/or modify it under the terms of the:
4+
#
5+
# Eclipse Public License version 1.0, or
6+
# GNU General Public License version 2, or
7+
# GNU Lesser General Public License version 2.1.
8+
9+
require_relative '../ruby/spec_helper'
10+
11+
require 'bigdecimal'
12+
13+
describe "BigDecimal" do
14+
15+
describe "bug GR-16506" do
16+
17+
before :each do
18+
@a = BigDecimal('166.666666666')
19+
@b = Rational(500, 3)
20+
@c = @a - @b
21+
end
22+
23+
# Check the input is as we understand it
24+
25+
it "has the LHS print as expected" do
26+
@a.to_s.should == "0.166666666666e3"
27+
@a.to_f.to_s.should == "166.666666666"
28+
Float(@a).to_s.should == "166.666666666"
29+
end
30+
31+
it "has the RHS print as expected" do
32+
@b.to_s.should == "500/3"
33+
@b.to_f.to_s.should == "166.66666666666666"
34+
Float(@b).to_s.should == "166.66666666666666"
35+
end
36+
37+
it "has the expected precision on the LHS" do
38+
@a.precs[0].should == 18
39+
end
40+
41+
it "has the expected maximum precision on the LHS" do
42+
@a.precs[1].should == 27
43+
end
44+
45+
it "produces the expected result when done via Float" do
46+
(Float(@a) - Float(@b)).to_s.should == "-6.666596163995564e-10"
47+
end
48+
49+
it "produces the expected result when done via to_f" do
50+
(@a.to_f - @b.to_f).to_s.should == "-6.666596163995564e-10"
51+
end
52+
53+
# Check underlying methods work as we understand
54+
55+
it "BigDecimal precision is the number of digits rounded up to a multiple of nine" do
56+
1.upto(100) do |n|
57+
b = BigDecimal('4' * n)
58+
precs, _ = b.precs
59+
(precs >= 9).should be_true
60+
(precs >= n).should be_true
61+
(precs % 9).should == 0
62+
end
63+
BigDecimal('NaN').precs[0].should == 9
64+
end
65+
66+
it "BigDecimal maximum precision is nine more than precision except for abnormals" do
67+
1.upto(100) do |n|
68+
b = BigDecimal('4' * n)
69+
precs, max = b.precs
70+
max.should == precs + 9
71+
end
72+
BigDecimal('NaN').precs[1].should == 9
73+
end
74+
75+
it "BigDecimal(Rational, 18) produces the result we expect" do
76+
BigDecimal(@b, 18).to_s.should == "0.166666666666666667e3"
77+
end
78+
79+
it "BigDecimal(Rational, BigDecimal.precs[0]) produces the result we expect" do
80+
BigDecimal(@b, @a.precs[0]).to_s.should == "0.166666666666666667e3"
81+
end
82+
83+
# Check the top-level expression works as we expect
84+
85+
it "produces a BigDecimal" do
86+
@c.class.should == BigDecimal
87+
end
88+
89+
it "produces the expected result" do
90+
@c.should == BigDecimal("-0.666667e-9")
91+
@c.to_s.should == "-0.666667e-9"
92+
end
93+
94+
it "produces the correct class for other artihmetic operators" do
95+
(@a + @b).class.should == BigDecimal
96+
(@a * @b).class.should == BigDecimal
97+
(@a / @b).class.should == BigDecimal
98+
(@a % @b).class.should == BigDecimal
99+
end
100+
101+
end
102+
103+
end

src/main/java/org/truffleruby/core/CoreLibrary.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ public class CoreLibrary {
176176
private final DynamicObject truffleRegexpOperationsModule;
177177
private final DynamicObject truffleThreadOperationsModule;
178178
private final DynamicObject bigDecimalClass;
179+
private final DynamicObject bigDecimalOperationsModule;
179180
private final DynamicObject encodingCompatibilityErrorClass;
180181
private final DynamicObject encodingUndefinedConversionErrorClass;
181182
private final DynamicObject methodClass;
@@ -546,6 +547,7 @@ public CoreLibrary(RubyContext context) {
546547

547548
bigDecimalClass = defineClass(numericClass, "BigDecimal");
548549
Layouts.CLASS.setInstanceFactoryUnsafe(bigDecimalClass, Layouts.BIG_DECIMAL.createBigDecimalShape(bigDecimalClass, bigDecimalClass));
550+
bigDecimalOperationsModule = defineModule(truffleModule, "BigDecimalOperations");
549551

550552
truffleFFIModule = defineModule(truffleModule, "FFI");
551553
DynamicObject truffleFFIAbstractMemoryClass = defineClass(truffleFFIModule, objectClass, "AbstractMemory");
@@ -955,6 +957,10 @@ public DynamicObject getBigDecimalClass() {
955957
return bigDecimalClass;
956958
}
957959

960+
public DynamicObject getBigDecimalOperationsModule() {
961+
return bigDecimalOperationsModule;
962+
}
963+
958964
public DynamicObjectFactory getBindingFactory() {
959965
return bindingFactory;
960966
}

src/main/java/org/truffleruby/core/exception/CoreExceptions.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ public DynamicObject argumentError(Rope message, Node currentNode, Throwable jav
198198
return ExceptionOperations.createRubyException(context, exceptionClass, StringOperations.createString(context, message), currentNode, javaThrowable);
199199
}
200200

201-
@TruffleBoundary
202201
public DynamicObject argumentErrorInvalidBigDecimal(String string, Node currentNode) {
203202
return argumentError(StringUtils.format("invalid value for BigDecimal(): \"%s\"", string), currentNode);
204203
}

src/main/java/org/truffleruby/stdlib/bigdecimal/AbstractAddNode.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public abstract class AbstractAddNode extends BigDecimalOpNode {
2222
private final ConditionProfile nanProfile = ConditionProfile.createBinaryProfile();
2323
private final ConditionProfile posInfinityProfile = ConditionProfile.createBinaryProfile();
2424
private final ConditionProfile negInfinityProfile = ConditionProfile.createBinaryProfile();
25-
private final ConditionProfile aNormalProfile = ConditionProfile.createBinaryProfile();
25+
private final ConditionProfile normalProfile = ConditionProfile.createBinaryProfile();
2626

2727
protected Object add(DynamicObject a, DynamicObject b, int precision) {
2828
if (precision == 0) {
@@ -53,7 +53,7 @@ protected Object addSpecial(DynamicObject a, DynamicObject b, int precision) {
5353

5454
// One is NEGATIVE_ZERO and second is NORMAL
5555

56-
if (aNormalProfile.profile(isNormal(a))) {
56+
if (normalProfile.profile(isNormal(a))) {
5757
return a;
5858
} else {
5959
return b;

src/main/java/org/truffleruby/stdlib/bigdecimal/AbstractSubNode.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,7 @@ public abstract class AbstractSubNode extends BigDecimalOpNode {
2424
private final ConditionProfile negInfinityProfile = ConditionProfile.createBinaryProfile();
2525
private final ConditionProfile normalProfile = ConditionProfile.createBinaryProfile();
2626

27-
@TruffleBoundary
28-
private BigDecimal subBigDecimal(DynamicObject a, DynamicObject b, MathContext mathContext) {
29-
return Layouts.BIG_DECIMAL.getValue(a).subtract(Layouts.BIG_DECIMAL.getValue(b), mathContext);
30-
}
31-
32-
protected Object subNormal(DynamicObject a, DynamicObject b, int precision) {
27+
protected Object sub(DynamicObject a, DynamicObject b, int precision) {
3328
if (precision == 0) {
3429
precision = getLimit();
3530
}
@@ -64,4 +59,10 @@ protected Object subSpecial(DynamicObject a, DynamicObject b, int precision) {
6459
return createBigDecimal(Layouts.BIG_DECIMAL.getValue(b).negate());
6560
}
6661
}
62+
63+
@TruffleBoundary
64+
private BigDecimal subBigDecimal(DynamicObject a, DynamicObject b, MathContext mathContext) {
65+
return Layouts.BIG_DECIMAL.getValue(a).subtract(Layouts.BIG_DECIMAL.getValue(b), mathContext);
66+
}
67+
6768
}

0 commit comments

Comments
 (0)