1 /**
2   Provide a 2^N-bit integer type.
3   Guaranteed to never allocate and expected binary layout
4   Recursive implementation with very slow division.
5  
6   <b>Supports all operations that builtin integers support.</b>
7   
8   Bugs: it's not sure if the unsigned operand would take precedence in a comparison/division.
9   TODO: add literals.  
10  */
11 module gfm.math.wideint;
12 
13 import std.traits,
14        std.ascii;
15 
16 static if( __VERSION__ < 2066 ) private enum nogc = 1;
17 
18 /// Wide signed integer.
19 /// Params:
20 ///    bits = number of bits, must be a power of 2.
21 template wideint(int bits)
22 {
23     alias integer!(true, bits) wideint;
24 }
25 
26 /// Wide unsigned integer.
27 /// Params:
28 ///    bits = number of bits, must be a power of 2.
29 template uwideint(int bits)
30 {
31     alias integer!(false, bits) uwideint;
32 }
33 
34 // Some predefined integers (any power of 2 greater than 128 would work)
35 
36 alias wideint!128 int128; // cent and ucent!
37 alias uwideint!128 uint128;
38 
39 alias wideint!256 int256;
40 alias uwideint!256 uint256;
41 
42 /// Use this template to get an arbitrary sized integer type.
43 private template integer(bool signed, int bits)
44 {
45     static assert((bits & (bits - 1)) == 0); // bits must be a power of 2
46 
47     // forward to native type for lower numbers of bits
48     static if (bits == 8)
49     {
50         static if (signed)
51             alias byte integer;
52         else
53             alias ubyte integer;
54     }
55     else static if (bits == 16)
56     {
57         static if (signed)
58             alias short integer;
59         else
60             alias ushort integer;
61     }
62     else static if (bits == 32)
63     {
64         static if (signed)
65             alias int integer;
66         else
67             alias uint integer;
68     }
69     else static if (bits == 64)
70     {
71         static if (signed)
72             alias long integer;
73         else
74             alias ulong integer;
75     }
76     else 
77     {
78         alias wideIntImpl!(signed, bits) integer;
79     }
80 }
81 
82 /// Recursive 2^n integer implementation.
83 align(1) struct wideIntImpl(bool signed, int bits)
84 {
85     align(1):
86     static assert(bits >= 128);
87     private
88     {
89         alias wideIntImpl self;
90 
91         template isSelf(T)
92         {
93             enum bool isSelf = is(Unqual!T == self);
94         }
95 
96         alias integer!(true, bits/2) sub_int_t;   // signed bits/2 integer
97         alias integer!(false, bits/2) sub_uint_t; // unsigned bits/2 integer
98 
99         alias integer!(true, bits/4) sub_sub_int_t;   // signed bits/4 integer
100         alias integer!(false, bits/4) sub_sub_uint_t; // unsigned bits/4 integer
101 
102         static if(signed)
103             alias sub_int_t hi_t; // hi_t has same signedness as the whole struct
104         else
105             alias sub_uint_t hi_t;
106 
107         alias sub_uint_t low_t;   // low_t is always unsigned
108 
109         enum _isWideIntImpl = true,
110              _bits = bits,
111              _signed = signed;
112     }
113 
114     /// Construct from a value.
115     @nogc this(T)(T x) pure nothrow
116     {
117         opAssign!T(x);
118     }
119 
120     /// Assign with a smaller unsigned type.
121     @nogc ref self opAssign(T)(T n) pure nothrow if (isIntegral!T && isUnsigned!T)
122     {
123         hi = 0;
124         lo = n;
125         return this;
126     }
127 
128     /// Assign with a smaller signed type (sign is extended).
129     @nogc ref self opAssign(T)(T n) pure nothrow if (isIntegral!T && isSigned!T)
130     {
131         // shorter int always gets sign-extended,
132         // regardless of the larger int being signed or not
133         hi = (n < 0) ? cast(hi_t)(-1) : cast(hi_t)0;
134 
135         // will also sign extend as well if needed
136         lo = cast(sub_int_t)n;
137         return this;
138     }
139 
140     /// Assign with a wide integer of the same size (sign is lost).
141     @nogc ref self opAssign(T)(T n) pure nothrow if (is(typeof(T._isWideIntImpl)) && T._bits == bits) 
142     {
143         hi = n.hi;
144         lo = n.lo;
145         return this;
146     }
147 
148     /// Assign with a smaller wide integer (sign is extended accordingly).
149     @nogc ref self opAssign(T)(T n) pure nothrow if (is(typeof(T._isWideIntImpl)) && T._bits < bits) 
150     {
151         static if (T._signed)
152         {
153             // shorter int always gets sign-extended,
154             // regardless of the larger int being signed or not
155             hi = cast(hi_t)((n < 0) ? -1 : 0);
156 
157             // will also sign extend as well if needed
158             lo = cast(sub_int_t)n;
159             return this;            
160         }
161         else
162         {
163             hi = 0;
164             lo = n;
165             return this;
166         }
167     }
168 
169     /// Cast to a smaller integer type (truncation).
170     @nogc T opCast(T)() pure const nothrow if (isIntegral!T)
171     {
172         return cast(T)lo;
173     }
174 
175     /// Cast to bool.
176     @nogc T opCast(T)() pure const nothrow if (is(T == bool))
177     {
178         return this != 0;
179     }
180 
181     /// Cast to wide integer of any size.
182     @nogc T opCast(T)() pure const nothrow if (is(typeof(T._isWideIntImpl)))
183     {
184         static if (T._bits < bits)
185             return cast(T)lo;
186         else
187             return T(this);
188     }
189 
190     /// Converts to a hexadecimal string.
191     string toString() pure const nothrow
192     {
193         string outbuff = "0x";
194         enum hexdigits = bits / 8;
195 
196         for (int i = 0; i < hexdigits; ++i)
197         {
198             outbuff ~= hexDigits[cast(int)((hi >> ((15 - i) * 4)) & 15)];
199         }
200         for (int i = 0; i < hexdigits; ++i)
201         {
202             outbuff ~= hexDigits[cast(int)((lo >> ((15 - i) * 4)) & 15)];
203         }
204         return outbuff;
205     }
206 
207     @nogc self opBinary(string op, T)(T o) pure const nothrow if (!isSelf!T)
208     {
209         self r = this;
210         self y = o;
211         return r.opOpAssign!(op)(y);
212     }
213 
214     @nogc self opBinary(string op, T)(T y) pure const nothrow if (isSelf!T)
215     {
216         self r = this; // copy
217         self o = y;
218         return r.opOpAssign!(op)(o);
219     }
220 
221     @nogc ref self opOpAssign(string op, T)(T y) pure nothrow if (!isSelf!T)
222     {
223         const(self) o = y;
224         return opOpAssign!(op)(o);
225     }
226 
227     @nogc ref self opOpAssign(string op, T)(T y) pure nothrow if (isSelf!T)
228     {
229         static if (op == "+")
230         {
231             hi += y.hi;
232             if (lo + y.lo < lo) // deal with overflow
233                 ++hi;
234             lo += y.lo;
235         }
236         else static if (op == "-")
237         {
238             opOpAssign!"+"(-y);
239         }
240         else static if (op == "<<")
241         {
242             if (y >= bits)
243             {
244                 hi = 0;
245                 lo = 0;
246             }
247             else if (y >= bits / 2)
248             {
249                 hi = lo << (y.lo - bits / 2);
250                 lo = 0;
251             }
252             else if (y > 0)
253             {
254                 hi = (lo >>> (-y.lo + bits / 2)) | (hi << y.lo);
255                 lo = lo << y.lo;
256             }
257         }
258         else static if (op == ">>" || op == ">>>")
259         {
260             assert(y >= 0);
261             static if (!signed || op == ">>>")
262                 immutable(sub_int_t) signFill = 0;
263             else
264                 immutable(sub_int_t) signFill = cast(sub_int_t)(isNegative() ? -1 : 0);
265 
266             if (y >= bits)
267             {
268                 hi = signFill;
269                 lo = signFill;
270             }
271             else if (y >= bits/2)
272             {
273                 lo = hi >> (y.lo - bits/2);
274                 hi = signFill;
275             }
276             else if (y > 0)
277             {
278                 lo = (hi << (-y.lo + bits/2)) | (lo >> y.lo);
279                 hi = hi >> y.lo;
280             }
281         }
282         else static if (op == "*")
283         {
284             sub_sub_uint_t[4] a = toParts();
285             sub_sub_uint_t[4] b = y.toParts();
286 
287             this = 0;
288             for(int i = 0; i < 4; ++i)
289                 for(int j = 0; j < 4 - i; ++j)
290                     this += self(cast(sub_uint_t)(a[i]) * b[j]) << ((bits/4) * (i + j));
291         }
292         else static if (op == "&")
293         {
294             hi &= y.hi;
295             lo &= y.lo;
296         }
297         else static if (op == "|")
298         {
299             hi |= y.hi;
300             lo |= y.lo;
301         }
302         else static if (op == "^")
303         {
304             hi ^= y.hi;
305             lo ^= y.lo;
306         }
307         else static if (op == "/" || op == "%")
308         {
309             self q = void, r = void;
310             static if(signed)
311                 Internals!bits.signedDivide(this, y, q, r);
312             else
313                 Internals!bits.unsignedDivide(this, y, q, r);
314             static if (op == "/")
315                 this = q;
316             else
317                 this = r;
318         }
319         else
320         {
321             static assert(false, "unsupported operation '" ~ op ~ "'");
322         }
323         return this;
324     }
325 
326     // const unary operations
327     @nogc self opUnary(string op)() pure const nothrow if (op == "+" || op == "-" || op == "~")
328     {
329         static if (op == "-")
330         {
331             self r = this;
332             r.not();
333             r.increment();
334             return r;
335         }
336         else static if (op == "+")
337            return this;
338         else static if (op == "~")
339         {
340             self r = this;
341             r.not();
342             return r;
343         }
344     }
345 
346     // non-const unary operations
347     @nogc self opUnary(string op)() pure nothrow if (op == "++" || op == "--")
348     {
349         static if (op == "++")
350             increment();
351         else static if (op == "--")
352             decrement();
353         return this;
354     }
355 
356     @nogc bool opEquals(T)(T y) pure const if (!isSelf!T)
357     {
358         return this == self(y);
359     }
360 
361     @nogc bool opEquals(T)(T y) pure const if (isSelf!T)
362     {
363        return lo == y.lo && y.hi == hi;
364     }
365 
366     @nogc int opCmp(T)(T y) pure const if (!isSelf!T)
367     {
368         return opCmp(self(y));
369     }
370 
371     @nogc int opCmp(T)(T y) pure const if (isSelf!T)
372     {
373         if (hi < y.hi) return -1;
374         if (hi > y.hi) return 1;
375         if (lo < y.lo) return -1;
376         if (lo > y.lo) return 1;
377         return 0;
378     }
379 
380     // binary layout should be what is expected on this platform
381     version (LittleEndian)
382     {
383         low_t lo;
384         hi_t hi;
385     }
386     else
387     {
388         hi_t hi;
389         low_t lo;
390     }
391 
392     private
393     {
394         static if (signed)
395         {
396             @nogc bool isNegative() pure nothrow const
397             {
398                 return signBit();
399             }
400         }
401         else
402         {
403             @nogc bool isNegative() pure nothrow const
404             {
405                 return false;
406             }
407         }
408 
409         @nogc void not() pure nothrow
410         {
411             hi = ~hi;
412             lo = ~lo;
413         }
414 
415         @nogc void increment() pure nothrow
416         {
417             ++lo;
418             if (lo == 0) ++hi;
419         }
420 
421         @nogc void decrement() pure nothrow
422         {
423             if (lo == 0) --hi;
424             --lo;
425         }
426 
427         @nogc bool signBit() pure const nothrow
428         {
429             enum SIGN_SHIFT = bits / 2 - 1;
430             return ((hi >> SIGN_SHIFT) & 1) != 0;
431         }
432 
433         @nogc sub_sub_uint_t[4] toParts() pure const nothrow
434         {
435             sub_sub_uint_t[4] p = void;
436             enum SHIFT = bits / 4;
437             immutable lomask = cast(sub_uint_t)(cast(sub_sub_int_t)(-1));
438             p[3] = cast(sub_sub_uint_t)(hi >> SHIFT);
439             p[2] = cast(sub_sub_uint_t)(hi & lomask);
440             p[1] = cast(sub_sub_uint_t)(lo >> SHIFT);
441             p[0] = cast(sub_sub_uint_t)(lo & lomask);
442             return p;
443         }
444     }
445 }
446 
447 @nogc public wideIntImpl!(signed, bits) abs(bool signed, int bits)(wideIntImpl!(signed, bits) x) pure nothrow
448 {
449     if(x >= 0)
450         return x;
451     else
452         return -x;
453 }
454 
455 private struct Internals(int bits)
456 {    
457     alias wideIntImpl!(true, bits) wint_t;
458     alias wideIntImpl!(false, bits) uwint_t;
459 
460     @nogc static void unsignedDivide(uwint_t dividend, uwint_t divisor,
461                                      out uwint_t quotient, out uwint_t remainder) pure nothrow
462     {
463         assert(divisor != 0);
464 
465         uwint_t rQuotient = 0;
466         uwint_t cDividend = dividend;
467 
468         while (divisor <= cDividend)
469         {
470             // find N so that (divisor << N) <= cDividend && cDividend < (divisor << (N + 1) )
471 
472             uwint_t N = 0;
473             uwint_t cDivisor = divisor;
474             while (cDividend > cDivisor) 
475             {
476                 if (cDivisor.signBit())
477                     break;
478 
479                 if (cDividend < (cDivisor << 1))
480                     break;
481 
482                 cDivisor <<= 1;
483                 ++N;
484             }
485             cDividend = cDividend - cDivisor;
486             rQuotient += (uwint_t(1) << N);
487         }
488 
489         quotient = rQuotient;
490         remainder = cDividend;
491     }
492 
493     @nogc static void signedDivide(wint_t dividend, wint_t divisor,
494                                    out wint_t quotient, out wint_t remainder) pure nothrow 
495     {
496         uwint_t q, r;
497         unsignedDivide(uwint_t(abs(dividend)), uwint_t(abs(divisor)), q, r);
498 
499         // remainder has same sign as the dividend
500         if (dividend < 0)
501             r = -r;
502 
503         // negate the quotient if opposite signs
504         if ((dividend >= 0) != (divisor >= 0))
505             q = -q;
506 
507         quotient = q;
508         remainder = r;
509 
510         assert(remainder == 0 || ((remainder < 0) == (dividend < 0)));
511     }
512 }
513 
514 unittest
515 {
516     long step = 164703072086692425;
517     for (long si = long.min; si <= long.max - step; si += step)
518     {
519         for (long sj = long.min; sj <= long.max - step; sj += step)
520         {
521             ulong ui = cast(ulong)si;
522             ulong uj = cast(ulong)sj;
523             int128 csi = si;
524             uint128 cui = si;
525             int128 csj = sj;
526             uint128 cuj = sj;
527             assert(csi == csi);
528             assert(~~csi == csi);
529             assert(-(-csi) == csi);
530             assert(++csi == si + 1);
531             assert(--csi == si);
532 
533             string testSigned(string op)
534             {
535                 return "assert(cast(ulong)(si" ~ op ~ "sj) == cast(ulong)(csi" ~ op ~ "csj));";
536             }
537 
538             string testMixed(string op)
539             {
540                 return "assert(cast(ulong)(ui" ~ op ~ "sj) == cast(ulong)(cui" ~ op ~ "csj));"
541                      ~ "assert(cast(ulong)(si" ~ op ~ "uj) == cast(ulong)(csi" ~ op ~ "cuj));";
542             }
543 
544             string testUnsigned(string op)
545             {
546                 return "assert(cast(ulong)(ui" ~ op ~ "uj) == cast(ulong)(cui" ~ op ~ "cuj));";
547             }
548 
549             string testAll(string op)
550             {
551                 return testSigned(op) ~ testMixed(op) ~ testUnsigned(op);
552             }
553 
554             mixin(testAll("+"));
555             mixin(testAll("-"));
556             mixin(testAll("*"));
557             mixin(testAll("|"));
558             mixin(testAll("&"));
559             mixin(testAll("^"));
560             if (sj != 0)
561             {
562                 mixin(testSigned("/"));
563                 mixin(testSigned("%"));
564                 if (si >= 0 && sj >= 0)
565                 {
566                     // those operations are not supposed to be the same at
567                     // higher bitdepth: a sign-extended negative may yield higher dividend
568                     testMixed("/");
569                     testUnsigned("/");
570                     testMixed("%");
571                     testUnsigned("%");
572                 }
573             }
574         }
575     }
576 }