Skip to content

Commit

Permalink
removes redundant hash implementation from BoxesRunTime.java
Browse files Browse the repository at this point in the history
  • Loading branch information
dgruntz committed May 9, 2012
1 parent 58bb2d1 commit b591910
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 68 deletions.
61 changes: 0 additions & 61 deletions src/library/scala/runtime/BoxesRunTime.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -203,67 +203,6 @@ private static boolean equalsNumChar(java.lang.Number xn, java.lang.Character yc
} }
} }


/** Hashcode algorithm is driven by the requirements imposed
* by primitive equality semantics, namely that equal objects
* have equal hashCodes. The first priority are the integral/char
* types, which already have the same hashCodes for the same
* values except for Long. So Long's hashCode is altered to
* conform to Int's for all values in Int's range.
*
* Float is problematic because it's far too small to hold
* all the Ints, so for instance Int.MaxValue.toFloat claims
* to be == to each of the largest 64 Ints. There is no way
* to preserve equals/hashCode alignment without compromising
* the hashCode distribution, so Floats are only guaranteed
* to have the same hashCode for whole Floats in the range
* Short.MinValue to Short.MaxValue (2^16 total.)
*
* Double has its hashCode altered to match the entire Int range,
* but is not guaranteed beyond that. (But could/should it be?
* The hashCode is only 32 bits so this is a more tractable
* issue than Float's, but it might be better simply to exclude it.)
*
* Note: BigInt and BigDecimal, being arbitrary precision, could
* be made consistent with all other types for the Int range, but
* as yet have not.
*
* Note: Among primitives, Float.NaN != Float.NaN, but the boxed
* verisons are equal. This still needs reconciliation.
*/
public static int hashFromLong(java.lang.Long n) {
int iv = n.intValue();
if (iv == n.longValue()) return iv;
else return n.hashCode();
}
public static int hashFromDouble(java.lang.Double n) {
int iv = n.intValue();
double dv = n.doubleValue();
if (iv == dv) return iv;

long lv = n.longValue();
if (lv == dv) return java.lang.Long.valueOf(lv).hashCode();
else return n.hashCode();
}
public static int hashFromFloat(java.lang.Float n) {
int iv = n.intValue();
float fv = n.floatValue();
if (iv == fv) return iv;

long lv = n.longValue();
if (lv == fv) return java.lang.Long.valueOf(lv).hashCode();
else return n.hashCode();
}
public static int hashFromNumber(java.lang.Number n) {
if (n instanceof java.lang.Long) return hashFromLong((java.lang.Long)n);
else if (n instanceof java.lang.Double) return hashFromDouble((java.lang.Double)n);
else if (n instanceof java.lang.Float) return hashFromFloat((java.lang.Float)n);
else return n.hashCode();
}
public static int hashFromObject(Object a) {
if (a instanceof Number) return hashFromNumber((Number)a);
else return a.hashCode();
}

private static int unboxCharOrInt(Object arg1, int code) { private static int unboxCharOrInt(Object arg1, int code) {
if (code == CHAR) if (code == CHAR)
return ((java.lang.Character) arg1).charValue(); return ((java.lang.Character) arg1).charValue();
Expand Down
30 changes: 28 additions & 2 deletions src/library/scala/runtime/ScalaRunTime.scala
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -233,12 +233,39 @@ object ScalaRunTime {
// //
// Note that these are the implementations called by ##, so they // Note that these are the implementations called by ##, so they
// must not call ## themselves. // must not call ## themselves.
//
// Hashcode algorithm is driven by the requirements imposed
// by primitive equality semantics, namely that equal objects
// have equal hashCodes. The first priority are the integral/char
// types, which already have the same hashCodes for the same
// values except for Long. So Long's hashCode is altered to
// conform to Int's for all values in Int's range.
//
// Float is problematic because it's far too small to hold
// all the Ints, so for instance Int.MaxValue.toFloat claims
// to be == to each of the largest 64 Ints. There is no way
// to preserve equals/hashCode alignment without compromising
// the hashCode distribution, so Floats are only guaranteed
// to have the same hashCode for whole Floats in the range
// Short.MinValue to Short.MaxValue (2^16 total.)
//
// Double has its hashCode altered to match the entire Int range,
// but is not guaranteed beyond that. (But could/should it be?
// The hashCode is only 32 bits so this is a more tractable
// issue than Float's, but it might be better simply to exclude it.)
//
// Note: BigInt and BigDecimal, being arbitrary precision, could
// be made consistent with all other types for the Int range, but
// as yet have not.
//
// Note: Among primitives, Float.NaN != Float.NaN, but the boxed
// verisons are equal. This still needs reconciliation.


@inline def hash(x: Any): Int = x match { @inline def hash(x: Any): Int = x match {
case null => 0 case null => 0
case x: Long => hash(x)
case x: Double => hash(x) case x: Double => hash(x)
case x: Float => hash(x) case x: Float => hash(x)
case x: java.lang.Number => hash(x)
case _ => x.hashCode case _ => x.hashCode
} }


Expand Down Expand Up @@ -266,7 +293,6 @@ object ScalaRunTime {
val high = (lv >>> 32).toInt val high = (lv >>> 32).toInt
low ^ (high + lowSign) low ^ (high + lowSign)
} }
@inline def hash(x: Number): Int = runtime.BoxesRunTime.hashFromNumber(x)


// The remaining overloads are here for completeness, but the compiler // The remaining overloads are here for completeness, but the compiler
// inlines these definitions directly so they're not generally used. // inlines these definitions directly so they're not generally used.
Expand Down
Original file line number Original file line Diff line number Diff line change
@@ -1,23 +1,23 @@
// This only tests direct access to the methods in BoxesRunTime, // This only tests direct access to the methods in ScalaRunTime,
// not the whole scheme. // not the whole scheme.
object Test object Test
{ {
import java.{ lang => jl } import java.{ lang => jl }
import scala.runtime.BoxesRunTime.{ hashFromNumber, hashFromObject } import scala.runtime.ScalaRunTime.{ hash }


def allSame[T](xs: List[T]) = assert(xs.distinct.size == 1, "failed: " + xs) def allSame[T](xs: List[T]) = assert(xs.distinct.size == 1, "failed: " + xs)


def mkNumbers(x: Int): List[Number] = def mkNumbers(x: Int): List[Number] =
List(x.toByte, x.toShort, x, x.toLong, x.toFloat, x.toDouble) List(x.toByte, x.toShort, x, x.toLong, x.toFloat, x.toDouble)


def testLDF(x: Long) = allSame(List[Number](x, x.toDouble, x.toFloat) map hashFromNumber) def testLDF(x: Long) = allSame(List[Number](x, x.toDouble, x.toFloat) map hash)


def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
List(Byte.MinValue, -1, 0, 1, Byte.MaxValue) foreach { n => List(Byte.MinValue, -1, 0, 1, Byte.MaxValue) foreach { n =>
val hashes = mkNumbers(n) map hashFromNumber val hashes = mkNumbers(n) map hash
allSame(hashes) allSame(hashes)
if (n >= 0) { if (n >= 0) {
val charCode = hashFromObject(n.toChar: Character) val charCode = hash(n.toChar: Character)
assert(charCode == hashes.head) assert(charCode == hashes.head)
} }
} }
Expand Down
8 changes: 8 additions & 0 deletions test/files/run/hashhash.scala
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@ object Test {


val x = (BigInt(1) << 64).toDouble val x = (BigInt(1) << 64).toDouble
val y: Any = x val y: Any = x
val f: Float = x.toFloat
val jn: java.lang.Number = x
val jf: java.lang.Float = x.toFloat
val jd: java.lang.Double = x


assert(x.## == y.##, ((x, y))) assert(x.## == y.##, ((x, y)))
assert(x.## == f.##, ((x, f)))
assert(x.## == jn.##, ((x, jn)))
assert(x.## == jf.##, ((x, jf)))
assert(x.## == jd.##, ((x, jd)))
} }
} }

0 comments on commit b591910

Please sign in to comment.