1 /** Bijections between scalar types and integers. 2 * 3 * TOOD extract reinterpret!T(x) 4 * 5 * TODO: support `real`? 6 */ 7 module nxt.bijections; 8 9 import std.meta : AliasSeq, staticIndexOf; 10 import std.traits : Unqual, isUnsigned, isSigned, isIntegral, Unsigned; 11 12 /** List of types that are bijectable to builtin integral types. */ 13 alias IntegralBijectableTypes = AliasSeq!(bool, char, wchar, dchar, 14 ubyte, ushort, uint, ulong, 15 byte, short, int, long, 16 float, double); 17 18 enum isIntegralBijectableType(T) = staticIndexOf!(Unqual!T, IntegralBijectableTypes) >= 0; 19 20 /// check that `bijectToUnsigned` preserves orderness, that is is a bijection 21 @safe unittest { 22 import std.random : Random, uniform; 23 auto gen = Random(); 24 25 enum maxCount = 1e4; 26 import std.algorithm : min; 27 28 static int cmp(T)(T x, T y) => x < y ? -1 : x > y ? 1 : 0; 29 30 foreach (T; AliasSeq!(ubyte, ushort, uint, ulong, byte, short, int, long)) { 31 foreach (i; 0 .. min(maxCount, T.max - T.min)) { 32 const x = uniform(T.min, T.max, gen); 33 const y = uniform(T.min, T.max, gen); 34 35 const expected = cmp(x, y); 36 const result = cmp(x.bijectToUnsigned, y.bijectToUnsigned); 37 38 assert(result == expected); 39 } 40 } 41 42 foreach (T; AliasSeq!(float, double)) { 43 foreach (i; 0 .. maxCount) { 44 const T x = uniform(-1e20, +1e20, gen); 45 const T y = uniform(-1e20, +1e20, gen); 46 47 // import nxt.debugio; 48 // dbg(x, ",", y); 49 50 const expected = cmp(x, y); 51 const result = cmp(x.bijectToUnsigned, y.bijectToUnsigned); 52 53 assert(result == expected); 54 } 55 } 56 } 57 58 pragma(inline) pure nothrow @safe @nogc: 59 60 /** Biject (shift) a signed `a` "up" to the corresponding unsigned type (for 61 * instance before radix sorting an array of `a`). 62 */ 63 auto bijectToUnsigned(T)(T a) @trusted 64 if (isIntegralBijectableType!T) { 65 alias UT = Unqual!T; 66 static if (is(UT == bool)) { return *(cast(ubyte*)&a); } // reinterpret 67 else static if (is(UT == char)) { return *(cast(ubyte*)&a); } // reinterpret 68 else static if (is(UT == wchar)) { return *(cast(ushort*)&a); } // reinterpret 69 else static if (is(UT == dchar)) { return *(cast(uint*)&a); } // reinterpret 70 else static if (isIntegral!UT) { 71 static if (isSigned!UT) { 72 alias UUT = Unsigned!UT; 73 return cast(UUT)(a + (cast(UUT)1 << (8*UT.sizeof - 1))); // "add up"" 74 } 75 else static if (isUnsigned!UT) 76 return a; // identity 77 else 78 static assert(0, "Unsupported integral input type " ~ UT.stringof); 79 } 80 else static if (is(UT == float)) { return ff(*cast(uint*)(&a)); } 81 else static if (is(UT == double)) { return ff(*cast(ulong*)(&a)); } 82 else static assert(0, "Unsupported input type " ~ UT.stringof); 83 } 84 85 /** Same as `bijectToUnsigned` with extra argument `descending` that reverses 86 * order. 87 */ 88 auto bijectToUnsigned(T)(T a, bool descending) 89 if (isIntegralBijectableType!T) { 90 immutable ua = a.bijectToUnsigned; 91 return descending ? ua.max-ua : ua; 92 } 93 94 /** Biject (Shift) an unsigned `a` "back down" to the corresponding signed type 95 * (for instance after radix sorting an array of `a`). 96 */ 97 void bijectFromUnsigned(U)(U a, ref U b) if (isUnsigned!U) { 98 b = a; /// Identity. 99 } 100 101 /// ditto 102 void bijectFromUnsigned(U, V)(U a, ref V b) 103 if (isUnsigned!U && 104 isIntegral!V && isSigned!V && 105 is(U == Unsigned!V)) { 106 b = a - (cast(Unsigned!U)1 << (8*U.sizeof - 1)); // "add down"" 107 } 108 109 /// ditto 110 @trusted void bijectFromUnsigned(ubyte a, ref bool b) { b = *cast(typeof(b)*)(&a); } 111 /// ditto 112 @trusted void bijectFromUnsigned(ubyte a, ref char b) { b = *cast(typeof(b)*)(&a); } 113 /// ditto 114 @trusted void bijectFromUnsigned(ushort a, ref wchar b) { b = *cast(typeof(b)*)(&a); } 115 /// ditto 116 @trusted void bijectFromUnsigned(ulong a, ref dchar b) { b = *cast(typeof(b)*)(&a); } 117 /// ditto 118 @trusted void bijectFromUnsigned(uint a, ref float b) { uint t = iff(a); b = *cast(float*)(&t); } 119 /// ditto 120 @trusted void bijectFromUnsigned(ulong a, ref double b) { ulong t = iff(a); b = *cast(double*)(&t); } 121 122 /// check that `bijectToUnsigned` is the opposite of `bijectFromUnsigned` 123 pure nothrow @safe @nogc unittest { 124 foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) { 125 static assert(is(typeof(T.init.bijectToUnsigned) == T)); 126 } 127 128 foreach (T; AliasSeq!(byte, short, int, long)) { 129 static assert(is(typeof(T.init.bijectToUnsigned) == Unsigned!T)); 130 } 131 132 static assert(is(typeof(char.init.bijectToUnsigned) == ubyte)); 133 static assert(is(typeof(wchar.init.bijectToUnsigned) == ushort)); 134 static assert(is(typeof(dchar.init.bijectToUnsigned) == uint)); 135 136 const n = 1_000_000; 137 foreach (const i; 0 .. n) { 138 foreach (T; AliasSeq!(bool, ubyte, ushort, uint, ulong, byte, short, 139 int, long, char, wchar, dchar, float, double)) { 140 const T x = cast(T) i; 141 auto y = x.bijectToUnsigned; 142 // pragma(msg, "T:", T); 143 // pragma(msg, "typeof(x):", typeof(x)); 144 // pragma(msg, "typeof(y):", typeof(y)); 145 T z; 146 y.bijectFromUnsigned(z); 147 assert(x == z); 148 } 149 } 150 } 151 152 /** Map bits of floating point number \p a to unsigned integer that can be radix sorted. 153 * 154 * Also finds \em sign of \p a. 155 * 156 * - if it's 1 (negative float), it flips all bits. 157 * - if it's 0 (positive float), it flips the sign only. 158 */ 159 uint ff(uint f) => f ^ (-cast(uint) (f >> (8*f.sizeof-1)) | 0x80000000); 160 /// ditto 161 ulong ff(ulong f) => f ^ (-cast(ulong) (f >> (8*f.sizeof-1)) | 0x8000000000000000); 162 163 /** Map a floating point number \p a back from radix sorting 164 * 165 * (Inverse of \c radix_flip_float()). 166 * 167 * - if sign is 1 (negative), it flips the sign bit back 168 * - if sign is 0 (positive), it flips all bits back 169 */ 170 uint iff(uint f) => f ^ (((f >> (8*f.sizeof-1)) - 1) | 0x80000000); 171 /// ditto 172 ulong iff(ulong f) => f ^ (((f >> (8*f.sizeof-1)) - 1) | 0x8000000000000000);