1 /** Various hash functions, including integer ones. 2 * 3 * Test: dmd -version=show -preview=dip1000 -preview=in -vcolumns -mcpu=native -checkaction=context -debug -g -unittest -main -I.. -i -run hash_functions.d 4 */ 5 module nxt.hash_functions; 6 7 @safe nothrow: 8 9 /** See_Also: http://forum.dlang.org/post/o1igoc$21ma$1@digitalmars.com 10 * 11 * Doesn't work: integers are returned as is. 12 */ 13 pragma(inline, true) 14 size_t typeidHashOf(T)(in T x) @trusted => typeid(T).getHash(&x); 15 16 /// 17 @safe nothrow unittest { 18 scope x = typeidHashOf(cast(int)17); 19 } 20 21 pure @nogc: 22 23 pragma(inline, true) 24 hash_t hashOfTypeInfoPtr(TypeInfo_Class typeinfo) @trusted pure nothrow @nogc 25 in(typeof(typeinfo).alignof == 8) 26 => (cast(hash_t)(cast(void*)typeinfo) >> 3); 27 28 /** Hash that incorporates the hash of `typeid` bit-xored with `hashOf(a)`. 29 * 30 * See_Also: https://forum.dlang.org/post/lxqoknwuujbymolnlyfw@forum.dlang.org 31 */ 32 pragma(inline, true) 33 hash_t hashOfPolymorphic(Class)(Class a) @trusted pure nothrow @nogc 34 if (is(Class == class)) { 35 static assert(typeid(Class).alignof == 8); 36 // const class_typeid_hash = (cast(hash_t)(cast(void*)typeid(Class)) >> 3) 37 return fibonacciHash(.hashOf(cast(void*)typeid(a))) ^ .hashOf(a); 38 } 39 40 pragma(inline, true) 41 size_t fibonacciHash(in hash_t hash) pure nothrow @safe @nogc 42 => (hash * 11400714819323198485LU); 43 44 version (unittest) { 45 private static: 46 class Thing {} 47 class Expr : Thing { 48 pure nothrow @safe @nogc: 49 alias Data = string; 50 this(Data data) scope { this.data = data; } 51 @property override hash_t toHash() const pure nothrow @safe @nogc => .hashOf(data); 52 Data data; 53 } 54 class NounExpr : Expr { 55 pure nothrow @safe @nogc: 56 this(Data data) scope { super(data); } 57 @property override hash_t toHash() const pure nothrow @safe @nogc => .hashOf(data); 58 } 59 } 60 61 /// 62 pure nothrow @safe @nogc unittest { 63 scope car1 = new Expr("car"); 64 scope car2 = new Expr("car"); 65 scope bar1 = new Expr("bar"); 66 scope ncar = new NounExpr("car"); 67 68 void testEqual() @trusted pure nothrow @nogc // TODO: make @safe when hashOf arg is scope 69 { 70 assert(hashOf(car1) == hashOf(car2)); 71 assert(hashOfPolymorphic(car1) == hashOfPolymorphic(car2)); 72 } 73 74 void testDifferent1() @trusted pure nothrow @nogc /+ TODO: make @safe when hashOf arg is scope +/ 75 { 76 assert(hashOf(car1) != hashOf(bar1)); 77 assert(hashOfPolymorphic(car1) != hashOfPolymorphic(bar1)); 78 } 79 80 void testDifferent2() @trusted pure nothrow @nogc /+ TODO: make @safe when hashOf arg is scope +/ 81 { 82 assert(hashOf(car1) == hashOf(ncar)); 83 assert(hashOfPolymorphic(car1) != hashOfPolymorphic(ncar)); 84 } 85 86 testEqual(); 87 testDifferent1(); 88 testDifferent2(); 89 } 90 91 @nogc: 92 93 /** Dummy-hash for benchmarking performance of HashSet. */ 94 pragma(inline, true) 95 hash_t identityHash64(in ulong x) pure nothrow @safe @nogc => x; // maps -1 to ulong.max 96 97 /// 98 pure nothrow @safe @nogc unittest { 99 assert(identityHash64(-1) == ulong.max); 100 assert(identityHash64(int.max) == int.max); 101 assert(identityHash64(ulong.max) == ulong.max); 102 } 103 104 /** Mueller integer hash function (bit mixer) A (32-bit). 105 * 106 * See_Also: https://stackoverflow.com/a/12996028/683710 107 * See_Also: http://zimbry.blogspot.se/2011/09/better-bit-mixing-improving-on.html 108 */ 109 uint muellerHash32(uint x) pure nothrow @safe @nogc { 110 version (D_Coverage) {} else pragma(inline, true); 111 x = ((x >> 16) ^ x) * 0x45d9f3b; 112 x = ((x >> 16) ^ x) * 0x45d9f3b; 113 x = (x >> 16) ^ x; 114 return x; 115 } 116 117 /** Mueller integer hash function (bit mixer) A (64-bit). 118 * 119 * Based on splitmix64, which seems to be based on the blog article "Better Bit 120 * Mixing" (mix 13). 121 * 122 * See_Also: https://stackoverflow.com/a/12996028/683710 123 * See_Also: http://zimbry.blogspot.se/2011/09/better-bit-mixing-improving-on.html 124 * See_Also: http://xorshift.di.unimi.it/splitmix64.c 125 */ 126 hash_t muellerHash64(ulong x) pure nothrow @safe @nogc { 127 version (D_Coverage) {} else pragma(inline, true); 128 x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9UL; 129 x = (x ^ (x >> 27)) * 0x94d049bb133111ebUL; 130 x = x ^ (x >> 31); 131 return x; 132 } 133 134 /** Thomas Wang 64-bit mix integer hash function. 135 * 136 * See_Also: https://gist.github.com/badboy/6267743#64-bit-mix-functions 137 */ 138 hash_t wangMixHash64(ulong x) pure nothrow @safe @nogc { 139 version (D_Coverage) {} else pragma(inline, true); 140 x = (~x) + (x << 21); // x = (x << 21) - x - 1; 141 x = x ^ (x >>> 24); 142 x = (x + (x << 3)) + (x << 8); // x * 265 143 x = x ^ (x >>> 14); 144 x = (x + (x << 2)) + (x << 4); // x * 21 145 x = x ^ (x >>> 28); 146 x = x + (x << 31); 147 return x; 148 } 149 150 /// 151 pure nothrow @safe @nogc unittest { 152 assert(wangMixHash64(0) == 8633297058295171728UL); 153 assert(wangMixHash64(1) == 6614235796240398542UL); 154 } 155 156 /** Inspired by Lemire's strongly universal hashing. 157 * 158 * See_Also: https://lemire.me/blog/2018/08/15/fast-strongly-universal-64-bit-hashing-everywhere/ 159 * 160 * Instead of shifts, we use rotations so we don't lose any bits. 161 * 162 * Added a final multiplcation with a constant for more mixing. It is most important that the 163 * lower bits are well mixed. 164 */ 165 hash_t lemireHash64(in ulong x) pure nothrow @safe @nogc { 166 import core.bitop : ror; 167 version (D_Coverage) {} else pragma(inline, true); 168 enum shift = 8*x.sizeof / 2; 169 const ulong h1 = x * 0x_A24B_AED4_963E_E407UL; 170 const ulong h2 = ror(x, shift) * 0x_9FB2_1C65_1E98_DF25UL; 171 const ulong h = ror(h1 + h2, shift); 172 return h; 173 } 174 /// ditto 175 hash_t lemireHash64(in double x) @trusted pure nothrow @nogc { 176 return lemireHash64(*(cast(ulong*)&x)); 177 } 178 179 /// 180 pure nothrow @safe @nogc unittest { 181 assert(lemireHash64(0) == 0UL); 182 assert(lemireHash64(1) == 10826341276197359097UL); 183 assert(lemireHash64(2) == 3205938474390199283UL); 184 assert(lemireHash64(0f) == 0UL); 185 assert(lemireHash64(1f) == 5597974336836488763); 186 assert(lemireHash64(2f) == 4611686018555721673UL); 187 } 188 189 uint lemireHash32(in uint x) pure nothrow @safe @nogc { 190 import core.bitop : ror; 191 version (D_Coverage) {} else pragma(inline, true); 192 enum shift = 8*x.sizeof / 2; 193 const uint h1 = x * 0x_963E_E407U; 194 const uint h2 = ror(x, shift) * 0x_1E98_DF25U; 195 const uint h = ror(h1 + h2, shift); 196 return h; 197 } 198 /// ditto 199 uint lemireHash32(in float x) @trusted pure nothrow @nogc { 200 return lemireHash32(*(cast(uint*)&x)); 201 } 202 203 /// 204 pure nothrow @safe @nogc unittest { 205 assert(lemireHash32(0) == 0UL); 206 assert(lemireHash32(1) == 3825694051); 207 assert(lemireHash32(2) == 3356420807); 208 assert(lemireHash32(0f) == 0UL); 209 assert(lemireHash32(1f) == 2910889945); 210 assert(lemireHash32(2f) == 1073805257); 211 } 212 213 /** Combine the hashes `lhs` and `rhs`. 214 * 215 * Copied from `boost::hash_combine`. 216 * See_Also: `dmd.root.hash.mixHash`. 217 */ 218 hash_t hashCombine(hash_t lhs, in hash_t rhs) pure nothrow @safe @nogc { 219 version (D_Coverage) {} else pragma(inline, true); 220 lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2); 221 return lhs; 222 } 223 alias mixHash = hashCombine; 224 225 /// 226 pure nothrow @safe @nogc unittest { 227 assert(hashCombine(8633297058295171728UL, 6614235796240398542UL) == 1903124116120912827UL); 228 }