diff options
-rw-r--r-- | src/library/scala/runtime/BoxesRunTime.java | 61 | ||||
-rw-r--r-- | src/library/scala/runtime/ScalaRunTime.scala | 30 | ||||
-rw-r--r-- | test/files/run/hashCodeBoxesRunTime.scala (renamed from test/files/run/hashCodeScalaRunTime.scala) | 10 | ||||
-rw-r--r-- | test/files/run/hashhash.scala | 8 |
4 files changed, 68 insertions, 41 deletions
diff --git a/src/library/scala/runtime/BoxesRunTime.java b/src/library/scala/runtime/BoxesRunTime.java index 0df196b2a6..258a176671 100644 --- a/src/library/scala/runtime/BoxesRunTime.java +++ b/src/library/scala/runtime/BoxesRunTime.java @@ -203,6 +203,67 @@ public final class BoxesRunTime } } + /** 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) { if (code == CHAR) return ((java.lang.Character) arg1).charValue(); diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 6bf25b8464..a04fd23710 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -233,39 +233,12 @@ object ScalaRunTime { // // Note that these are the implementations called by ##, so they // 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 - // versions are equal. This still needs reconciliation. @inline def hash(x: Any): Int = x match { case null => 0 - case x: Long => hash(x) case x: Double => hash(x) case x: Float => hash(x) + case x: java.lang.Number => hash(x) case _ => x.hashCode } @@ -293,6 +266,7 @@ object ScalaRunTime { val high = (lv >>> 32).toInt low ^ (high + lowSign) } + @inline def hash(x: Number): Int = runtime.BoxesRunTime.hashFromNumber(x) // The remaining overloads are here for completeness, but the compiler // inlines these definitions directly so they're not generally used. diff --git a/test/files/run/hashCodeScalaRunTime.scala b/test/files/run/hashCodeBoxesRunTime.scala index e352af95f1..081a73376e 100644 --- a/test/files/run/hashCodeScalaRunTime.scala +++ b/test/files/run/hashCodeBoxesRunTime.scala @@ -1,23 +1,23 @@ -// This only tests direct access to the methods in ScalaRunTime, +// This only tests direct access to the methods in BoxesRunTime, // not the whole scheme. object Test { import java.{ lang => jl } - import scala.runtime.ScalaRunTime.{ hash } + import scala.runtime.BoxesRunTime.{ hashFromNumber, hashFromObject } def allSame[T](xs: List[T]) = assert(xs.distinct.size == 1, "failed: " + xs) def mkNumbers(x: Int): List[Number] = 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 hash) + def testLDF(x: Long) = allSame(List[Number](x, x.toDouble, x.toFloat) map hashFromNumber) def main(args: Array[String]): Unit = { List(Byte.MinValue, -1, 0, 1, Byte.MaxValue) foreach { n => - val hashes = mkNumbers(n) map hash + val hashes = mkNumbers(n) map hashFromNumber allSame(hashes) if (n >= 0) { - val charCode = hash(n.toChar: Character) + val charCode = hashFromObject(n.toChar: Character) assert(charCode == hashes.head) } } diff --git a/test/files/run/hashhash.scala b/test/files/run/hashhash.scala index f9fc067398..dc31df8cfa 100644 --- a/test/files/run/hashhash.scala +++ b/test/files/run/hashhash.scala @@ -9,15 +9,7 @@ object Test { val x = (BigInt(1) << 64).toDouble 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.## == f.##, ((x, f))) - assert(x.## == jn.##, ((x, jn))) - assert(x.## == jf.##, ((x, jf))) - assert(x.## == jd.##, ((x, jd))) } } |