diff --git a/lib/pure/bitops.nim b/lib/pure/bitops.nim index 9ebdabb7b..e00ea3109 100644 --- a/lib/pure/bitops.nim +++ b/lib/pure/bitops.nim @@ -355,6 +355,15 @@ proc firstSetBit*(x: SomeInteger): int {.inline, noSideEffect.} = when sizeof(x) <= 4: result = firstSetBitNim(x.uint32) else: result = firstSetBitNim(x.uint64) +proc lastSetBit*(x: SomeUnsignedInt): uint {.inline, noSideEffect.} = + ## Returns the 1-based index of the most significant set bit of x. + ## If x == 0, the result will also be 0 + var temp = x + result = 0 + while temp != 0: + temp = temp shr 1 + result += 1 + proc fastLog2*(x: SomeInteger): int {.inline, noSideEffect.} = ## Quickly find the log base 2 of an integer. ## If `x` is zero, when ``noUndefinedBitOpts`` is set, result is -1, diff --git a/lib/pure/collections/hashcommon.nim b/lib/pure/collections/hashcommon.nim index d9a914afc..6281074af 100644 --- a/lib/pure/collections/hashcommon.nim +++ b/lib/pure/collections/hashcommon.nim @@ -9,6 +9,7 @@ # An ``include`` file which contains common code for # hash sets and tables. +from bitops import lastSetBit const growthFactor = 2 @@ -51,7 +52,7 @@ proc rawGetKnownHC[X, A](t: X, key: A, hc: Hash): int {.inline.} = rawGetKnownHCImpl() template genHashImpl(key, hc: typed) = - hc = hash(key) + hc = hash(key, targetBits=lastSetBit(maxHash(t).uint)) if hc == 0: # This almost never taken branch should be very predictable. hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine. diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim index ac8498517..9a511ce9d 100644 --- a/lib/pure/hashes.nim +++ b/lib/pure/hashes.nim @@ -113,31 +113,65 @@ proc hash*[T: proc](x: T): Hash {.inline.} = result = hash(pointer(x)) const - prime = uint(11) + defaultSeedA = (17316035218449499591'u64).uint + defaultSeedB = (1734880652122947187'u64).uint -proc hash*(x: int): Hash {.inline.} = +template computeIntegerHash(x, numBits, seedA, seedB): Hash = + cast[Hash]((seedA*cast[uint](x) + seedB) shr (sizeof(x)*8 - numBits*8)) + +proc hash*( + x: int, + targetBits: SomeUnsignedInt = sizeof(uint), + seedA = defaultSeedA, + seedB = defaultSeedB +): Hash {.inline.} = ## Efficient hashing of integers. - result = cast[Hash](cast[uint](x) * prime) + result = computeIntegerHash(x, targetBits, seedA, seedB) -proc hash*(x: int64): Hash {.inline.} = +proc hash*( + x: int64, + targetBits: SomeUnsignedInt = sizeof(uint), + seedA = defaultSeedA, + seedB = defaultSeedB +): Hash {.inline.} = ## Efficient hashing of `int64` integers. - result = cast[Hash](cast[uint](x) * prime) + result = computeIntegerHash(x, targetBits, seedA, seedB) -proc hash*(x: uint): Hash {.inline.} = +proc hash*( + x: uint, + targetBits: SomeUnsignedInt = sizeof(uint), + seedA = defaultSeedA, + seedB = defaultSeedB +): Hash {.inline.} = ## Efficient hashing of unsigned integers. - result = cast[Hash](x * prime) + result = computeIntegerHash(x, targetBits, seedA, seedB) -proc hash*(x: uint64): Hash {.inline.} = +proc hash*( + x: uint64, + targetBits: SomeUnsignedInt = sizeof(uint), + seedA = defaultSeedA, + seedB = defaultSeedB +): Hash {.inline.} = ## Efficient hashing of `uint64` integers. - result = cast[Hash](cast[uint](x) * prime) + result = computeIntegerHash(x, targetBits, seedA, seedB) -proc hash*(x: char): Hash {.inline.} = +proc hash*( + x: char, + targetBits: SomeUnsignedInt = sizeof(uint), + seedA = defaultSeedA, + seedB = defaultSeedB +): Hash {.inline.} = ## Efficient hashing of characters. - result = cast[Hash](cast[uint](ord(x)) * prime) + result = computeIntegerHash(ord(x), targetBits, seedA, seedB) -proc hash*[T: Ordinal](x: T): Hash {.inline.} = +proc hash*[T: Ordinal]( + x: T, + targetBits: SomeUnsignedInt = sizeof(uint), + seedA = defaultSeedA, + seedB = defaultSeedB +): Hash {.inline.} = ## Efficient hashing of other ordinal types (e.g. enums). - result = cast[Hash](cast[uint](ord(x)) * prime) + result = computeIntegerHash(x, targetBits, seedA, seedB) proc hash*(x: float): Hash {.inline.} = ## Efficient hashing of floats. diff --git a/tests/collections/thashsets.nim b/tests/collections/thashsets.nim index cd4401511..bccf4654f 100644 --- a/tests/collections/thashsets.nim +++ b/tests/collections/thashsets.nim @@ -1,4 +1,4 @@ -import sets, hashes, algorithm +import sets, hashes, algorithm, bitops block setEquality: @@ -120,3 +120,13 @@ block hashForOrderdSet: reversed = !$reversed doAssert hash(r) == reversed doAssert hash(s1) != reversed + +block hashDoesntHaveLowerBitsCleared: + let num32 = 0xFFFF.uint32 + let bits = num32.lastBitSet + + let h1 = hash(0xFFFFFFFF00000000'u64, targetBits=bits) + let h2 = hash(0x2345123400000000'u64, targetBits=bits) + + + doAssert (h1 and num32) != (h2 and num32)