1 module mecca.lib.integers; 2 3 // Licensed under the Boost license. Full copyright information in the AUTHORS file 4 5 import std.traits; 6 import std.meta: AliasSeq; 7 import std.conv; 8 import std..string; 9 import std.algorithm: among; 10 11 import mecca.lib.exception : DBG_ASSERT; 12 13 version(unittest) import mecca.lib.exception : assertEQ, assertThrows; 14 15 import mecca.log; 16 import mecca.lib.consts; 17 18 version(LDC) { 19 ubyte bswap(ubyte x) pure @nogc { 20 pragma(inline, true); 21 return x; 22 } 23 ushort bswap(ushort x) pure @nogc { 24 pragma(inline, true); 25 if (__ctfe) { 26 return cast(ushort)((x << 8) | (x >> 8)); 27 } 28 else { 29 import ldc.intrinsics: llvm_bswap; 30 return llvm_bswap(x); 31 } 32 } 33 uint bswap(uint x) pure @nogc { 34 pragma(inline, true); 35 if (__ctfe) { 36 import core.bitop: bswap; 37 return bswap(x); 38 } 39 else { 40 import ldc.intrinsics: llvm_bswap; 41 return llvm_bswap(x); 42 } 43 } 44 ulong bswap(ulong x) pure @nogc { 45 pragma(inline, true); 46 if (__ctfe) { 47 import core.bitop: bswap; 48 return bswap(x); 49 } 50 else { 51 import ldc.intrinsics: llvm_bswap; 52 return llvm_bswap(x); 53 } 54 } 55 } 56 else { 57 public import core.bitop: bswap; 58 ubyte bswap(ubyte x) pure @nogc {return x;} 59 ushort bswap(ushort x) pure @nogc {return cast(ushort)((x << 8) | (x >> 8));} 60 } 61 62 unittest { 63 assert(bswap(ubyte(0x12)) == 0x12); 64 assert(bswap(ushort(0x1122)) == 0x2211); 65 assert(bswap(uint(0x11223344)) == 0x44332211); 66 assert(bswap(ulong(0x1122334455667788UL)) == 0x8877665544332211UL); 67 } 68 69 version (LittleEndian) { 70 enum hostOrder = 'L'; 71 } 72 else version (BigEndian) { 73 enum hostOrder = 'B'; 74 } 75 else { 76 static assert (false); 77 } 78 enum networkOrder = 'B'; 79 80 private U toEndianity(char E1, char E2, U)(U val) pure @nogc 81 if (isIntegral!U && (E1 == 'L' || E1 == 'B') && (E2 == 'L' || E2 == 'B')) { 82 pragma(inline, true); 83 static if (E1 == E2) { 84 return val; 85 } 86 else { 87 return bswap(val); 88 } 89 } 90 private U endianOp(char E1, string op, char E2, U)(U lhs, U rhs) { 91 pragma(inline, true); 92 return cast(U)(mixin("toEndianity!(E1, hostOrder)(lhs)" ~ op ~ "toEndianity!(E2, hostOrder)(rhs)")); 93 } 94 95 96 struct Integer(T, char E) { 97 static assert (E == 'L' || E == 'B'); 98 static assert (is(T == byte) || is(T == ubyte) || is(T == short) || is(T == ushort) || 99 is(T == int) || is(T == uint) || is(T == long) || is(T == ulong), T); 100 enum signed = isSigned!T; 101 enum bits = T.sizeof * 8; 102 enum name = (signed ? "S" : "U") ~ E ~ bits.to!string; 103 enum Integer min = Integer(T.min); 104 enum Integer max = Integer(T.max); 105 106 T value; 107 108 static assert (this.sizeof == T.sizeof); 109 110 this(T val) { 111 value = toEndianity!(E, hostOrder)(val); 112 } 113 this(U, char E2)(Integer!(U, E2) val) { 114 pragma(inline, true); 115 opAssign!(U, E2)(val); 116 } 117 @property T inHostOrder() const pure @nogc { 118 pragma(inline, true); 119 return toEndianity!(E, hostOrder, T)(value); 120 } 121 122 U opCast(U)() { 123 static if (is(U == bool)) { 124 return value != 0; 125 } 126 static if (isIntegral!U) { 127 return cast(U)value; 128 } 129 else if (isInstanceOf!(Integer, U)) { 130 return U(cast(S)toHostOrder); 131 } 132 else { 133 static assert (false, U); 134 } 135 } 136 137 auto ref opAssign(T rhs) { 138 value = toEndianity!(E, hostOrder)(rhs); 139 return this; 140 } 141 auto ref opAssign(U, char E2)(Integer!(U, E2) rhs) if (signed == rhs.signed) { 142 static assert (U.sizeof <= T.sizeof); 143 value = toEndianity!(E, E2)(rhs.value); 144 return this; 145 } 146 147 bool opEquals(T rhs) { 148 pragma(inline, true); 149 return inHostOrder == rhs; 150 } 151 bool opEquals(U, char E2)(Integer!(U, E2) rhs) const if (signed == rhs.signed) { 152 pragma(inline, true); 153 return inHostOrder == rhs.inHostOrder; 154 } 155 156 long opCmp(T rhs) { 157 pragma(inline, true); 158 return inHostOrder < rhs ? -1 : 1; 159 } 160 long opCmp(U, char E2)(Integer!(U, E2) rhs) const if (signed == rhs.signed) { 161 pragma(inline, true); 162 return inHostOrder < rhs.inHostOrder ? -1 : 1; 163 } 164 165 auto ref opUnary(string op: "++")() { 166 pragma(inline, true); 167 static if (E == hostOrder) { 168 ++value; 169 } 170 else { 171 opAssign(endianOp!(E, "+", hostOrder)(value, cast(T)1)); 172 } 173 return this; 174 } 175 auto ref opUnary(string op: "--")() { 176 static if (E == hostOrder) { 177 --value; 178 } 179 else { 180 opAssign(endianOp!(E, "+", hostOrder)(value, cast(T)1)); 181 } 182 return this; 183 } 184 185 static if (signed) { 186 auto opUnary(string op)() const if (op == "+" || op == "-") { 187 pragma(inline, true); 188 // Make the integer promotion cast explicit, to avoid compiler deprecation messages. 189 static if (bits < 32) { 190 enum promotionCast = "cast(int)"; 191 } 192 else { 193 enum promotionCast = ""; 194 } 195 return Integer(cast(T) mixin(op ~ promotionCast ~ "inHostOrder")); 196 } 197 198 private alias binOps = AliasSeq!("+", "-", "*", "/", "%", "^"); 199 } 200 else { 201 auto opUnary(string op)() const if (op == "~") { 202 pragma(inline, true); 203 // Make the integer promotion cast explicit, to avoid compiler deprecation messages. 204 static if (bits < 32) { 205 enum promotionCast = "cast(uint)"; 206 } 207 else { 208 enum promotionCast = ""; 209 } 210 return Integer(cast(T) mixin(op ~ promotionCast ~ "inHostOrder")); 211 } 212 213 private alias binOps = AliasSeq!("+", "-", "*", "/", "%", "^", "&", "|", "^", "<<", ">>", ">>>"); 214 } 215 216 auto opBinary(string op)(T rhs) const if (op.among(binOps)) { 217 pragma(inline, true); 218 return Integer(endianOp!(E, op, hostOrder)(value, rhs)); 219 } 220 auto opBinary(string op, U, char E2)(Integer!(U, E2) rhs) const if (signed == rhs.signed && op.among(binOps)) { 221 pragma(inline, true); 222 static if (U.sizeof <= T.sizeof) { 223 return Integer(endianOp!(E, op, E2)(value, rhs.value)); 224 } 225 else { 226 return Integer!(U, E)(endianOp!(E, op, E2)(cast(U)value, rhs.value)); 227 } 228 } 229 230 string toString() const { 231 return "%s#%s".format(value, name); 232 } 233 } 234 235 alias U8 = Integer!(ubyte, hostOrder); 236 alias U16 = Integer!(ushort, hostOrder); 237 alias U32 = Integer!(uint, hostOrder); 238 alias U64 = Integer!(ulong, hostOrder); 239 240 alias S8 = Integer!(byte, hostOrder); 241 alias S16 = Integer!(short, hostOrder); 242 alias S32 = Integer!(int, hostOrder); 243 alias S64 = Integer!(long, hostOrder); 244 245 unittest { 246 auto x = 17.U8; 247 auto y = 17.S8; 248 auto z = 17.U16; 249 auto x64 = 17.U64; 250 auto y64 = 17.S64; 251 auto w = (-128).S8; 252 253 static assert (!is(typeof(x + y))); 254 static assert (!is(typeof(x > y))); 255 static assert (is(typeof(x > z) == bool)); 256 static assert (is(typeof(~x))); 257 static assert (is(typeof(x ^ z))); 258 static assert (is(typeof(-y))); 259 static assert (!is(typeof(x ^ y))); 260 static assert (!is(typeof(~y))); 261 262 assert (x + z == 34); 263 assert (x * 2 == 34); 264 assert (y * 2 == 34); 265 assert (z * 2 == 34); 266 assert (-y == -17); 267 assert (~x64 == 0xFFFF_FFFF_FFFF_FFEE); 268 assert (-y64 == -long(17) ); 269 assert (-w == -128); // We don't want the result to be promoted to int (==128). 270 271 x++; 272 y++; 273 assert (x == 18); 274 } 275 276 alias NetU8 = Integer!(ubyte, networkOrder); 277 alias NetU16 = Integer!(ushort, networkOrder); 278 alias NetU32 = Integer!(uint, networkOrder); 279 alias NetU64 = Integer!(ulong, networkOrder); 280 281 alias NetS8 = Integer!(byte, networkOrder); 282 alias NetS16 = Integer!(short, networkOrder); 283 alias NetS32 = Integer!(int, networkOrder); 284 alias NetS64 = Integer!(long, networkOrder); 285 286 unittest { 287 import std.stdio; 288 289 NetU16 x = 17; 290 U16 y = x; 291 assert (x == y); 292 assert (x.value == 0x1100); 293 assert (y.value == 0x0011); 294 295 assert (x.toString == "4352#UB16"); 296 assert (y.toString == "17#UL16"); 297 298 auto z = x + y; 299 static assert (is(typeof(z) == NetU16)); 300 assert (z.value == 0x2200); 301 auto w = x * 2; 302 static assert (is(typeof(w) == NetU16)); 303 304 assert (y * 2 == 34); 305 assert (x * 2 == w); 306 307 w++; 308 assert (w.value == 0x2300); 309 } 310 311 312 struct SerialInteger(T) { 313 static assert (isUnsigned!T, "Type must be an unsigned integer"); 314 315 alias Type = T; 316 enum min = T.min; 317 enum max = T.max; 318 enum T midpoint = (2 ^^ (BITS_IN_BYTE * T.sizeof - 1)); 319 T value; 320 321 this(T value) nothrow @nogc { 322 this.value = value; 323 } 324 325 U opCast(U)() nothrow @nogc if (isUnsigned!U) { 326 return this.value; 327 } 328 329 ref SerialInteger opAssign(SerialInteger rhs) nothrow @nogc {pragma(inline, true); 330 this.value = rhs.value; 331 return this; 332 } 333 ref SerialInteger opAssign(T rhs) nothrow @nogc {pragma(inline, true); 334 this.value = rhs; 335 return this; 336 } 337 338 ref SerialInteger opUnary(string op)() nothrow @nogc if (op == "++" || op == "--") {pragma(inline, true); 339 mixin(op ~ "value;"); 340 return this; 341 } 342 SerialInteger opBinary(string op)(T rhs) const pure nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true); 343 return SerialInteger(cast(T)mixin("value " ~ op ~ " rhs")); 344 } 345 SerialInteger opBinary(string op)(SerialInteger rhs) const pure nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true); 346 return SerialInteger(cast(T)mixin("value " ~ op ~ " rhs.value")); 347 } 348 349 ref SerialInteger opOpAssign(string op)(T rhs) nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true); 350 mixin("value " ~ op ~ "= rhs;"); 351 return this; 352 } 353 ref SerialInteger opOpAssign(string op)(SerialInteger rhs) nothrow @nogc if (op == "+" || op == "-") {pragma(inline, true); 354 mixin("value " ~ op ~ "= rhs.value;"); 355 return this; 356 } 357 358 bool opEquals(const T rhs) const pure nothrow @nogc {pragma(inline, true); 359 return value == rhs; 360 } 361 bool opEquals(const SerialInteger rhs) const pure nothrow @nogc {pragma(inline, true); 362 return value == rhs.value; 363 } 364 365 int opCmp(const SerialInteger rhs) const pure nothrow @nogc {pragma(inline, true); 366 return opCmp(rhs.value); 367 } 368 int opCmp(const T rhs) const {pragma(inline, true); 369 pragma(inline, true); 370 // see https://en.wikipedia.org/wiki/Serial_number_arithmetic 371 // note that if the two numbers are on the circumference, then both (a < b) and (b < a) are true 372 // but it's too expensive to assert on it 373 static if (is(T == ubyte)) { 374 return cast(byte)(value - rhs); 375 } 376 else static if (is(T == ushort)) { 377 return cast(short)(value - rhs); 378 } 379 else static if (is(T == uint)) { 380 return cast(int)(value - rhs); 381 } 382 else { 383 static assert (false, "not implemented"); 384 } 385 } 386 387 static assert (this.sizeof == T.sizeof); 388 } 389 390 alias Serial8 = SerialInteger!ubyte; 391 alias Serial16 = SerialInteger!ushort; 392 alias Serial32 = SerialInteger!uint; 393 394 unittest { 395 auto a = Serial16(2); 396 a = 7; 397 assert(a < 100); 398 assert(Serial16(65530) < 100); 399 assert(100 > Serial16(65530)); 400 assert(a == 7); 401 a++; 402 a += 7; 403 assert(a == 15); 404 assert(a < 100); 405 assert(a > 10); 406 assert(Serial16(0) > Serial16(65534)); 407 408 // subtraction 409 a = Serial16(2); 410 assert( (a - cast(ushort)(-1)).value == 3 ); 411 assert( (a - cast(ushort)(-1)).value == cast(ushort)(a.value + 1) ); 412 413 assert( (a - 5).value == 65533 ); 414 assert( cast(short)((a - 5).value) == cast(short)-3 ); 415 } 416 417 /// Flip each bit of the input. Returns the same type as the input. 418 /// See https://dlang.org/changelog/2.078.0.html#fix16997 419 T bitComplement(T)(T val) pure nothrow @nogc { 420 static if (T.sizeof < int.sizeof) { 421 return cast(T)( ~ cast(int)val ); 422 } else { 423 return ~val; 424 } 425 } 426 427 unittest { 428 { 429 ulong sum = 1; 430 ubyte val = 0xFE; 431 sum += bitComplement(val); 432 assert(sum == 2); 433 } 434 { 435 ushort s1 = 0x0101; 436 ushort s2 = 0xF00F; 437 s1 &= bitComplement(s2); 438 assert(s1 == 0x0100); 439 } 440 441 static assert( bitComplement(0x0F) == 0xFFFF_FFF0 ); 442 static assert( bitComplement(ushort(0x0F)) == 0xFFF0 ); 443 } 444 445 /// Return the number of bits sufficient to cover a value 446 ubyte bitsInValue(ulong value) pure nothrow @safe @nogc { 447 ubyte ret; 448 449 while( value!=0 ) { 450 ret++; 451 value>>=1; 452 } 453 454 return ret; 455 } 456 457 unittest { 458 assertEQ(bitsInValue(1), 1); 459 assertEQ(bitsInValue(2), 2); 460 assertEQ(bitsInValue(0), 0); 461 assertEQ(bitsInValue(3), 2); 462 assertEQ(bitsInValue(5), 3); 463 } 464 465 /// Create a mask with `bits` set bits 466 @notrace T createBitMask(T = ulong)(uint numBits) if(isIntegral!T && isUnsigned!T) 467 { 468 DBG_ASSERT!"numBits=%s"(numBits <= (T.sizeof * 8), numBits); 469 470 // Avoid the corner cases that would require an "if" to support 471 static if( T.sizeof == ulong.sizeof ) { 472 DBG_ASSERT!"An 'all ones' mask is not supported by this function"(numBits < (T.sizeof * 8)); 473 } 474 475 return cast(T)((cast(T)1 << numBits) - 1); 476 } 477 478 unittest { 479 import std.exception; 480 import core.exception; 481 482 assertEQ(createBitMask(7), 0x7f); 483 assertEQ(createBitMask(8), 0xff); 484 assertEQ(createBitMask!ubyte(8), 0xff); 485 assertEQ(createBitMask(32), 0xffffffff); 486 assertThrown!AssertError(createBitMask!ulong(64)); 487 assertThrown!AssertError(createBitMask!ubyte(9)); 488 }