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