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