1 /** Various extensions to core.bitop and std.bitmanip.
2  *
3  * Copyright: Per Nordlöw 2022-.
4  * License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
5  * Authors: $(WEB Per Nordlöw)
6  *
7  * TODO: Add range checking of bit indexes.
8  *
9  * TODO: Make use of TZCNT and LZCNT either as inline assembly or as builtins: https://github.com/dlang/dmd/pull/6364
10  */
11 module nxt.bitop_ex;
12 
13 import std.meta : allSatisfy;
14 import std.traits : isIntegral;
15 
16 pure nothrow @safe @nogc:
17 
18 /** Get an unsigned type of size as `T` if possible. */
19 template UnsignedOfSameSizeAs(T) {
20 	enum nBits = 8*T.sizeof;
21 	static	  if (nBits ==  8) alias UnsignedOfSameSizeAs = ubyte;
22 	else static if (nBits == 16) alias UnsignedOfSameSizeAs = ushort;
23 	else static if (nBits == 32) alias UnsignedOfSameSizeAs = uint;
24 	else static if (nBits == 64) alias UnsignedOfSameSizeAs = ulong;
25 	else static if (nBits == 128) alias UnsignedOfSameSizeAs = ucent;
26 	else
27 	{
28 		import std.conv: to;
29 		static assert(0, "No Unsigned type of size " ~ to!string(nBits) ~ " found");
30 	}
31 }
32 
33 /** Returns: `T` with only `bix`:th bit set. */
34 T makeBit(T, I...)(I bixs) @safe
35 if (isIntegral!T &&
36 	allSatisfy!(isIntegral, I) &&
37 	I.length >= 1)
38 in
39 {
40 	foreach (n, const bix; bixs) {
41 		assert(0 <= bix && bix < 8*T.sizeof,
42 			   "Bit index " ~ n.stringof ~ " is out of range");
43 	}
44 }
45 do
46 {
47 	T x = 0;
48 	foreach (const bix; bixs)
49 		x |= cast(T)((cast(T)1) << bix);
50 	return x;
51 }
52 alias btm = makeBit;
53 
54 /** Returns: `true` iff all `bix`:th bits of `a` are set. */
55 pragma(inline, true)
56 bool testBit(T, I...)(in T a, I bixs) @safe
57 if (isIntegral!T &&
58 	allSatisfy!(isIntegral, I) &&
59 	I.length >= 1)
60 	=> a & makeBit!T(bixs) ? true : false;
61 
62 /** Returns: `true` iff all `bix`:th bits of `a` are set. */
63 pragma(inline, true)
64 bool testBit(T, I...)(in T a, I bixs) @trusted
65 if ((!(isIntegral!T)) &&
66 	allSatisfy!(isIntegral, I))
67 	=> (*(cast(UnsignedOfSameSizeAs!T*)&a)).testBit(bixs); // reuse integer variant
68 
69 /** Returns: `true` iff all `bix`:th bits of `*a` are set. */
70 pragma(inline, true)
71 bool testBit(T, I...)(in T* a, I bixs) @safe
72 if ((!(isIntegral!T)) &&
73 	!is(T == size_t) &&	 // avoid stealing `core.bitop.bt`
74 	allSatisfy!(isIntegral, I) &&
75 	I.length >= 1)
76 	=> testBit(*a, bixs);
77 alias bt = testBit;
78 
79 ///
80 pure nothrow @safe @nogc unittest {
81 	static void test(T)() {
82 		const mn = T.min, mx = T.max;
83 		enum nBits = 8*T.sizeof;
84 		foreach (const ix; 0 .. nBits-1)
85 			assert(!mn.bt(ix));
86 		assert(mn.bt(nBits - 1));
87 		foreach (const ix; 0 .. T.sizeof)
88 			assert(mx.bt(ix));
89 	}
90 	test!byte;
91 	test!short;
92 	test!int;
93 	test!long;
94 }
95 
96 /** Test and sets the `bix`:th bit of `a` to one.
97  *
98  * Returns: A non-zero value if the bit was set, and a zero if it was clear.
99 */
100 void setBit(T, I...)(ref T a, I bixs) @safe
101 if (isIntegral!T &&
102 	allSatisfy!(isIntegral, I) &&
103 	I.length >= 1) {
104 	pragma(inline, true);
105 	a |= makeBit!T(bixs);
106 }
107 
108 /** Sets the `bix`:th bit of `*a` to one.
109  */
110 void setBit(T, I...)(T* a, I bixs) @safe
111 if (isIntegral!T &&
112 	!is(T == size_t) && // avoid stealing core.bitop.bt
113 	allSatisfy!(isIntegral, I) &&
114 	I.length >= 1) {
115 	pragma(inline, true);
116 	*a |= makeBit!T(bixs);
117 }
118 
119 /** Sets the `bix`:th bit of `*a` to one.
120  */
121 void setBit(T, I...)(ref T a, I bixs) @trusted
122 if ((!(isIntegral!T)) &&
123 	allSatisfy!(isIntegral, I) &&
124 	I.length >= 1) {
125 	pragma(inline, true);
126 	alias U = UnsignedOfSameSizeAs!T;
127 	(*(cast(U*)&a)) |= makeBit!U(bixs); // reuse integer variant
128 }
129 alias bts = setBit;
130 
131 /* alias btc = complementBit; */
132 /* alias btr = resetBit; */
133 
134 /** Set lowest bit of `a` to one. */
135 pragma(inline, true)
136 void setLowestBit(T)(ref T a) @safe if (isIntegral!T) => setBit(a, 0);
137 alias setBottomBit = setLowestBit;
138 alias setLsbit = setLowestBit;
139 
140 /** Set highest bit of `a` to one. */
141 pragma(inline, true)
142 void setHighestBit(T)(ref T a) @safe if (isIntegral!T) => setBit(a, 8*T.sizeof - 1);
143 alias setTopBit = setHighestBit;
144 alias setMsbit = setHighestBit;
145 
146 /** Get lowest bit of `a`. */
147 pragma(inline, true)
148 bool getLowestBit(T)(in T a) @safe
149 if (isIntegral!T)
150 	=> (a & 1) != 0;
151 alias getBottomBit = getLowestBit;
152 alias getLsbit = getLowestBit;
153 
154 /** Get highest bit of `a`. */
155 pragma(inline, true)
156 bool getHighestBit(T)(in T a) @safe if (isIntegral!T) => (a & (cast(T)1 << 8*T.sizeof - 1)) != 0;	/+ TODO: use core.bitop.bt when T is a size_t +/
157 alias getTopBit = getHighestBit;
158 alias getMsbit = getHighestBit;
159 
160 ///
161 pure nothrow @safe @nogc unittest {
162 	const ubyte x = 1;
163 	assert(!x.getTopBit);
164 	assert(x.getLowestBit);
165 }
166 
167 ///
168 pure nothrow @safe @nogc unittest {
169 	const ubyte x = 128;
170 	assert(x.getTopBit);
171 	assert(!x.getLowestBit);
172 }
173 
174 /** Reset bits `I` of `a` (to zero). */
175 void resetBit(T, I...)(ref T a, I bixs) @safe
176 if (isIntegral!T &&
177 	allSatisfy!(isIntegral, I)) {
178 	pragma(inline, true);
179 	a &= ~makeBit!T(bixs);
180 }
181 
182 /** Reset bits `I` of `*a` (to zero). */
183 void resetBit(T, I...)(T* a, I bixs) @safe
184 if (isIntegral!T &&
185 	!is(T == size_t) && // avoid stealing core.bitop.bt
186 	allSatisfy!(isIntegral, I)) {
187 	pragma(inline, true);
188 	*a &= ~makeBit!T(bixs);
189 }
190 
191 /** Reset bits `I` of `a` (to zero). */
192 void resetBit(T, I...)(ref T a, I bixs)
193 if ((!(isIntegral!T)) &&
194 	allSatisfy!(isIntegral, I)) {
195 	pragma(inline, true);
196 	alias U = UnsignedOfSameSizeAs!T;
197 	(*(cast(U*)&a)) &= ~makeBit!U(bixs); // reuse integer variant
198 }
199 
200 /** Reset lowest bit of `a` (to zero). */
201 pragma(inline, true)
202 void resetLowestBit(T)(ref T a) @safe if (isIntegral!T) => resetBit(a, 0);
203 alias resetBottomBit = resetLowestBit;
204 
205 /** Reset highest bit of `a` (to zero). */
206 pragma(inline, true)
207 void resetHighestBit(T)(ref T a) @safe if (isIntegral!T) => resetBit(a, 8*T.sizeof - 1);
208 alias resetTopBit = resetHighestBit;
209 
210 alias btr = resetBit;
211 
212 ///
213 pure nothrow @nogc unittest {
214 	alias T = int;
215 	enum nBits = 8*T.sizeof;
216 	T a = 0;
217 
218 	a.bts(0); assert(a == 1);
219 	a.bts(1); assert(a == 3);
220 	a.bts(2); assert(a == 7);
221 
222 	a.btr(0); assert(a == 6);
223 	a.btr(1); assert(a == 4);
224 	a.btr(2); assert(a == 0);
225 
226 	a.bts(0, 1, 2); assert(a == 7);
227 	a.btr(0, 1, 2); assert(a == 0);
228 
229 	a.bts(8*T.sizeof - 1); assert(a != 0);
230 	a.btr(8*T.sizeof - 1); assert(a == 0);
231 
232 	T b = 0;
233 	b.bts(nBits - 1);
234 	assert(b == T.min);
235 }
236 
237 ///
238 pure nothrow @safe @nogc unittest {
239 	static void test(T)() {
240 		enum nBits = 8*T.sizeof;
241 		T x = 0;
242 		x.bts(0);
243 	}
244 
245 	test!float;
246 	test!double;
247 }
248 
249 ///
250 pure nothrow @safe @nogc unittest {
251 	assert(makeBit!int(2) == 4);
252 	assert(makeBit!int(2, 3) == 12);
253 	assert(makeBit!uint(0, 31) == 2^^31 + 1);
254 
255 	import std.meta : AliasSeq;
256 	foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) {
257 		foreach (const n; 0 .. 8*T.sizeof) {
258 			const x = makeBit!T(n);
259 			assert(x == 2UL^^n);
260 
261 			T y = x;
262 			y.resetBit(n);
263 			assert(y == 0);
264 
265 			y.setBit(n);
266 			assert(y == x);
267 		}
268 	}
269 }