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);