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 }