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 }