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