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