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         x |= cast(T)((cast(T)1) << bix);
52     return x;
53 }
54 alias btm = makeBit;
55 
56 /** Returns: `true` iff all `bix`:th bits of `a` are set. */
57 pragma(inline, true)
58 bool testBit(T, I...)(in T a, I bixs) @safe
59 if (isIntegral!T &&
60     allSatisfy!(isIntegral, I) &&
61     I.length >= 1)
62 	=> a & makeBit!T(bixs) ? true : false;
63 
64 /** Returns: `true` iff all `bix`:th bits of `a` are set. */
65 pragma(inline, true)
66 bool testBit(T, I...)(in T a, I bixs) @trusted
67 if ((!(isIntegral!T)) &&
68     allSatisfy!(isIntegral, I))
69 	=> (*(cast(UnsignedOfSameSizeAs!T*)&a)).testBit(bixs); // reuse integer variant
70 
71 /** Returns: `true` iff all `bix`:th bits of `*a` are set. */
72 pragma(inline, true)
73 bool testBit(T, I...)(in T* a, I bixs) @safe
74 if ((!(isIntegral!T)) &&
75     !is(T == size_t) &&     // avoid stealing `core.bitop.bt`
76     allSatisfy!(isIntegral, I) &&
77     I.length >= 1)
78 	=> testBit(*a, bixs);
79 alias bt = testBit;
80 
81 ///
82 @safe pure nothrow @nogc unittest
83 {
84     static void test(T)()
85     {
86         const mn = T.min, mx = T.max;
87         enum nBits = 8*T.sizeof;
88         foreach (const ix; 0 .. nBits-1)
89             assert(!mn.bt(ix));
90         assert(mn.bt(nBits - 1));
91         foreach (const ix; 0 .. T.sizeof)
92             assert(mx.bt(ix));
93     }
94     test!byte;
95     test!short;
96     test!int;
97     test!long;
98 }
99 
100 /** Test and sets the `bix`:th bit of `a` to one.
101  *
102  * Returns: A non-zero value if the bit was set, and a zero if it was clear.
103 */
104 void setBit(T, I...)(ref T a, I bixs) @safe
105 if (isIntegral!T &&
106     allSatisfy!(isIntegral, I) &&
107     I.length >= 1)
108 {
109     pragma(inline, true);
110     a |= makeBit!T(bixs);
111 }
112 
113 /** Sets the `bix`:th bit of `*a` to one.
114  */
115 void setBit(T, I...)(T* a, I bixs) @safe
116 if (isIntegral!T &&
117     !is(T == size_t) && // avoid stealing core.bitop.bt
118     allSatisfy!(isIntegral, I) &&
119     I.length >= 1)
120 {
121     pragma(inline, true);
122     *a |= makeBit!T(bixs);
123 }
124 
125 /** Sets the `bix`:th bit of `*a` to one.
126  */
127 void setBit(T, I...)(ref T a, I bixs) @trusted
128 if ((!(isIntegral!T)) &&
129     allSatisfy!(isIntegral, I) &&
130     I.length >= 1)
131 {
132     pragma(inline, true);
133     alias U = UnsignedOfSameSizeAs!T;
134     (*(cast(U*)&a)) |= makeBit!U(bixs); // reuse integer variant
135 }
136 alias bts = setBit;
137 
138 /* alias btc = complementBit; */
139 /* alias btr = resetBit; */
140 
141 /** Set lowest bit of `a` to one. */
142 pragma(inline, true)
143 void setLowestBit(T)(ref T a) @safe if (isIntegral!T) => setBit(a, 0);
144 alias setBottomBit = setLowestBit;
145 alias setLsbit = setLowestBit;
146 
147 /** Set highest bit of `a` to one. */
148 pragma(inline, true)
149 void setHighestBit(T)(ref T a) @safe if (isIntegral!T) => setBit(a, 8*T.sizeof - 1);
150 alias setTopBit = setHighestBit;
151 alias setMsbit = setHighestBit;
152 
153 /** Get lowest bit of `a`. */
154 pragma(inline, true)
155 bool getLowestBit(T)(in T a) @safe
156 if (isIntegral!T)
157 	=> (a & 1) != 0;
158 alias getBottomBit = getLowestBit;
159 alias getLsbit = getLowestBit;
160 
161 /** Get highest bit of `a`. */
162 pragma(inline, true)
163 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
164 alias getTopBit = getHighestBit;
165 alias getMsbit = getHighestBit;
166 
167 ///
168 @safe pure nothrow @nogc unittest
169 {
170     const ubyte x = 1;
171     assert(!x.getTopBit);
172     assert(x.getLowestBit);
173 }
174 
175 ///
176 @safe pure nothrow @nogc unittest
177 {
178     const ubyte x = 128;
179     assert(x.getTopBit);
180     assert(!x.getLowestBit);
181 }
182 
183 /** Reset bits `I` of `a` (to zero). */
184 void resetBit(T, I...)(ref T a, I bixs) @safe
185 if (isIntegral!T &&
186     allSatisfy!(isIntegral, I))
187 {
188     pragma(inline, true);
189     a &= ~makeBit!T(bixs);
190 }
191 
192 /** Reset bits `I` of `*a` (to zero). */
193 void resetBit(T, I...)(T* a, I bixs) @safe
194 if (isIntegral!T &&
195     !is(T == size_t) && // avoid stealing core.bitop.bt
196     allSatisfy!(isIntegral, I))
197 {
198     pragma(inline, true);
199     *a &= ~makeBit!T(bixs);
200 }
201 
202 /** Reset bits `I` of `a` (to zero). */
203 void resetBit(T, I...)(ref T a, I bixs)
204 if ((!(isIntegral!T)) &&
205     allSatisfy!(isIntegral, I))
206 {
207     pragma(inline, true);
208     alias U = UnsignedOfSameSizeAs!T;
209     (*(cast(U*)&a)) &= ~makeBit!U(bixs); // reuse integer variant
210 }
211 
212 /** Reset lowest bit of `a` (to zero). */
213 pragma(inline, true)
214 void resetLowestBit(T)(ref T a) @safe if (isIntegral!T) => resetBit(a, 0);
215 alias resetBottomBit = resetLowestBit;
216 
217 /** Reset highest bit of `a` (to zero). */
218 pragma(inline, true)
219 void resetHighestBit(T)(ref T a) @safe if (isIntegral!T) => resetBit(a, 8*T.sizeof - 1);
220 alias resetTopBit = resetHighestBit;
221 
222 alias btr = resetBit;
223 
224 ///
225 pure nothrow @nogc unittest
226 {
227     alias T = int;
228     enum nBits = 8*T.sizeof;
229     T a = 0;
230 
231     a.bts(0); assert(a == 1);
232     a.bts(1); assert(a == 3);
233     a.bts(2); assert(a == 7);
234 
235     a.btr(0); assert(a == 6);
236     a.btr(1); assert(a == 4);
237     a.btr(2); assert(a == 0);
238 
239     a.bts(0, 1, 2); assert(a == 7);
240     a.btr(0, 1, 2); assert(a == 0);
241 
242     a.bts(8*T.sizeof - 1); assert(a != 0);
243     a.btr(8*T.sizeof - 1); assert(a == 0);
244 
245     T b = 0;
246     b.bts(nBits - 1);
247     assert(b == T.min);
248 }
249 
250 ///
251 @safe pure nothrow @nogc unittest
252 {
253     static void test(T)()
254     {
255         enum nBits = 8*T.sizeof;
256         T x = 0;
257         x.bts(0);
258     }
259 
260     test!float;
261     test!double;
262 }
263 
264 ///
265 @safe pure nothrow @nogc unittest
266 {
267     assert(makeBit!int(2) == 4);
268     assert(makeBit!int(2, 3) == 12);
269     assert(makeBit!uint(0, 31) == 2^^31 + 1);
270 
271     import std.meta : AliasSeq;
272     foreach (T; AliasSeq!(ubyte, ushort, uint, ulong))
273     {
274         foreach (const n; 0 .. 8*T.sizeof)
275         {
276             const x = makeBit!T(n);
277             assert(x == 2UL^^n);
278 
279             T y = x;
280             y.resetBit(n);
281             assert(y == 0);
282 
283             y.setBit(n);
284             assert(y == x);
285         }
286     }
287 }