Skip to content

Commit 3b887a1

Browse files
committed
[GR-16086] BigDecimal objects with the same value should have the same hash code.
PullRequest: truffleruby/866
2 parents de9b954 + b0a4193 commit 3b887a1

File tree

3 files changed

+57
-0
lines changed

3 files changed

+57
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Bug fixes:
55
* Fixed `BigDecimal#{clone,dup}` so it now just returns the receiver, per Ruby 2.5+ semantics (#1680).
66
* Fixed creating `BigDecimal` instances from non-finite `Float` values (#1685).
77
* Fixed `BigDecimal#inspect` output for non-finite values (e.g, NaN or -Infinity) (#1683).
8+
* Fixed `BigDecimal#hash` to return the same value for two `BigDecimal` objects that are equal (#1656).
89
* Added missing `BigDecimal` constant definitions (#1684).
910
* Implemented `rb_eval_string_protect`.
1011
* Fixed `rb_get_kwargs` to correctly handle optional and rest arguments.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
require_relative '../../spec_helper'
2+
require 'bigdecimal'
3+
4+
describe "BidDecimal#hash" do
5+
describe "two BigDecimal objects with the same value" do
6+
it "should have the same hash for ordinary values" do
7+
BigDecimal('1.2920').hash.should == BigDecimal('1.2920').hash
8+
end
9+
10+
it "should have the same hash for infinite values" do
11+
BigDecimal("+Infinity").hash.should == BigDecimal("+Infinity").hash
12+
BigDecimal("-Infinity").hash.should == BigDecimal("-Infinity").hash
13+
end
14+
15+
it "should have the same hash for NaNs" do
16+
BigDecimal("NaN").hash.should == BigDecimal("NaN").hash
17+
end
18+
19+
it "should have the same hash for zero values" do
20+
BigDecimal("+0").hash.should == BigDecimal("+0").hash
21+
BigDecimal("-0").hash.should == BigDecimal("-0").hash
22+
end
23+
end
24+
25+
describe "two BigDecimal objects with numerically equal values" do
26+
it "should have the same hash value" do
27+
BigDecimal("1.2920").hash.should == BigDecimal("1.2920000").hash
28+
end
29+
end
30+
end

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,4 +1545,30 @@ public int toISpecial(DynamicObject value) {
15451545
}
15461546
}
15471547

1548+
@CoreMethod(names = "hash")
1549+
public abstract static class HashNode extends BigDecimalCoreMethodArrayArgumentsNode {
1550+
1551+
private static final int CLASS_SALT = 1468180038; // random number, stops hashes for similar values but different classes being the same, static because we want deterministic hashes.
1552+
1553+
@TruffleBoundary
1554+
@Specialization(guards = "isNormal(value)")
1555+
public Object hashNormal(DynamicObject value) {
1556+
// Ruby treats trailing zeroes as insignificant for hash calculation. Java's BigDecimal, however,
1557+
// may return different hash values for two numerically equivalent values with a different number
1558+
// of trailing zeroes. Stripping them away avoids the issue.
1559+
final BigDecimal bigDecimalValue = Layouts.BIG_DECIMAL.getValue(value).stripTrailingZeros();
1560+
1561+
return getContext().getHashing(this).hash(CLASS_SALT, bigDecimalValue.hashCode());
1562+
}
1563+
1564+
@TruffleBoundary
1565+
@Specialization(guards = "!isNormal(value)")
1566+
public Object hashSpecial(DynamicObject value) {
1567+
final BigDecimalType type = Layouts.BIG_DECIMAL.getType(value);
1568+
1569+
return getContext().getHashing(this).hash(CLASS_SALT, type.hashCode());
1570+
}
1571+
1572+
}
1573+
15481574
}

0 commit comments

Comments
 (0)