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 }