1 /*******************************************************************************
2 
3     Struct emulating a large (bigger than ulong) non-negative integer of fixed
4     range (defined via template argument).  `ulong` but still fixed in size
5     (defined via template argument).
6 
7     Copyright:
8         Copyright (c) 2017 dunnhumby Germany GmbH.
9         All rights reserved.
10 
11     License:
12         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13         Alternatively, this file may be distributed under the terms of the Tango
14         3-Clause BSD License (see LICENSE_BSD.txt for details).
15 
16 *******************************************************************************/
17 
18 module ocean.math.WideUInt;
19 
20 import ocean.transition;
21 import ocean.core.Test;
22 import ocean.core.Enforce;
23 import ocean.core.Verify;
24 
25 import core.stdc.math;
26 import ocean.math.IEEE : feqrel;
27 
28 /*******************************************************************************
29 
30     Struct emulating a large (bigger than ulong) non-negative integer of fixed
31     range (defined via template argument).  `ulong` but still fixed in size
32     (defined via template argument).
33 
34     Such struct is a value type with stable binary layout for a given size which
35     is the primary goal of this implementation. Performance may be eventually
36     improved if necessity arises, right now implementation is intentionally
37     simplistic and naive.
38 
39     Internally wide integer is represented by a static array of `uint` values
40     such that concatenating their binary representation together results in long
41     binary sequence representing actual stored number.
42 
43     Params:
44         N = amount of uint-size words to use as a backing storage for the
45             number
46 
47 *******************************************************************************/
48 
49 public struct WideUInt ( size_t N )
50 {
51     static assert (
52         N > 2,
53         "For 'N <= 2' using 'uint' or 'ulong' directly is suggested"
54     );
55 
56     /***************************************************************************
57 
58        Binary representation of the number can be formed by putting binary
59        representation of each individual word in `payload` side by
60        side starting from the `payload[$-1]`.
61 
62        `uint` is chosen as individual word type because multiplying two `uint`
63        words will result in `ulong` at most, making possible to use basic
64        integer arithmetic to implement most of wide integer arithmetic.
65 
66     ***************************************************************************/
67 
68     private uint[N] payload;
69 
70     /***********************************************************************
71 
72         Constructor from a regular ulong
73 
74     ***********************************************************************/
75 
76     this ( ulong value )
77     {
78         this.assign(value);
79     }
80 
81     /***************************************************************************
82 
83         Returns:
84             amount of meaningful decimal digits in currently stored value
85 
86     ***************************************************************************/
87 
88     public size_t decimal_digits ( )
89     {
90         if ((&this).opEquals(0))
91             return 1;
92 
93         WideUInt copy = this;
94 
95         size_t count;
96         while (copy != 0)
97         {
98             copy.divideBy(10);
99             ++count;
100         }
101         return count;
102     }
103 
104     unittest
105     {
106         WideUInt num = 0;
107         test!("==")(num.decimal_digits(), 1);
108 
109         num = 42;
110         test!("==")(num.decimal_digits(), 2);
111 
112         num = ulong.max;
113         test!("==")(num.decimal_digits(), 20);
114 
115         num.multiplyBy(100);
116         test!("==")(num.decimal_digits(), 22);
117     }
118 
119     /***************************************************************************
120 
121         Inefficient allocating string conversion useful for tests and
122         prototyping.
123 
124         Returns:
125             string representation
126 
127     ***************************************************************************/
128 
129     public istring toString ( )
130     {
131         mstring result;
132         (&this).toString((cstring s) { result ~= s; });
133         return assumeUnique(result);
134     }
135 
136     unittest
137     {
138         WideUInt num = 0;
139         test!("==")(num.toString(), "0");
140 
141         num = 42;
142         test!("==")(num.toString(), "42");
143 
144         num.payload[2] = 1;
145         test!("==")(num.toString(), "18446744073709551658");
146 
147         num = 0;
148         num.payload[2] = 1;
149         test!("==")(num.toString(), "18446744073709551616");
150     }
151 
152     /***************************************************************************
153 
154         Sink based string conversion
155 
156         Params:
157             sink = delegate to call with resulting string
158 
159     ***************************************************************************/
160 
161     public void toString ( scope void delegate (cstring) sink )
162     {
163         auto n = (&this).decimal_digits();
164         static mstring buffer;
165         buffer.length = n;
166         enableStomping(buffer);
167 
168         WideUInt copy = this;
169         for (ptrdiff_t idx = n-1; idx >= 0; --idx)
170         {
171             auto remainder = copy.divideBy(10);
172             buffer[idx] = cast(char) ('0' + remainder);
173         }
174 
175         sink(buffer);
176     }
177 
178     /***************************************************************************
179 
180         Enables assignment from a plain integer type
181 
182         Params:
183             rhs = value to assign
184 
185     ***************************************************************************/
186 
187     public void opAssign ( ulong rhs )
188     {
189         (&this).assign(rhs);
190     }
191 
192     unittest
193     {
194         WideUInt i;
195         i = 42;
196         test!("==")(i.payload[0], 42);
197         i = ulong.max;
198         test!("==")(i.payload[0], uint.max);
199         test!("==")(i.payload[1], uint.max);
200     }
201 
202     unittest
203     {
204         WideUInt a = 42;
205         WideUInt b = 43;
206         a = b;
207         test!("==")(a, 43);
208     }
209 
210     /***************************************************************************
211 
212         Enables equality comparison with a plain integer type
213 
214         Params:
215             rhs = value to compare to
216 
217         Returns:
218             value as defined by `opEquals` spec
219 
220     ***************************************************************************/
221 
222     public equals_t opEquals ( ulong rhs )
223     {
224         if ((&this).payload[0] != (rhs & uint.max))
225             return false;
226 
227         if ((&this).payload[1] != (rhs >> 32))
228             return false;
229 
230         foreach (ref word; (&this).payload[2 .. N])
231         {
232             if (word != 0)
233                 return false;
234         }
235 
236         return true;
237     }
238 
239     unittest
240     {
241         WideUInt i;
242 
243         i = 42;
244         test!("==")(i, 42);
245 
246         i = ulong.max / 2;
247         test!("==")(i, ulong.max / 2);
248         test!("!=")(i, ulong.max);
249 
250         i.payload[$-1] = 42;
251         test!("!=")(i, 42);
252 
253         i.payload[0] = 0;
254         test!("!=")(i, 42);
255     }
256 
257     /***************************************************************************
258 
259         Enables equality comparison with WideUInt of the same size
260 
261         Params:
262             rhs = value to compare against
263 
264         Returns:
265             value as defined by `opEquals` spec
266 
267     ***************************************************************************/
268 
269     public equals_t opEquals ( WideUInt rhs )
270     {
271         return (&this).payload[] == rhs.payload[];
272     }
273 
274     unittest
275     {
276         WideUInt a, b;
277         test!("==")(a, b);
278         foreach (size_t i, ref elem; a.payload)
279         {
280             a.payload[i] = 1337;
281             b.payload[i] = 1337;
282         }
283         test!("==")(a, b);
284         a.payload[$-1] = 0;
285         test!("!=")(a, b);
286     }
287 
288     /***************************************************************************
289 
290         Enables ordering comparison with WideUInt of the same size
291 
292         Params:
293             rhs = value to compare against
294 
295         Returns:
296             value as defined by `opCmp` spec
297 
298     ***************************************************************************/
299 
300     mixin(genOpCmp("
301     {
302         ptrdiff_t idx = N-1;
303         while (idx > 0)
304         {
305             if (this.payload[idx] != 0 || rhs.payload[idx] != 0)
306                 break;
307             --idx;
308         }
309 
310         auto a = this.payload[idx];
311         auto b = rhs.payload[idx];
312         return a < b ? -1 : (a > b ? 1 : 0);
313     }
314     "));
315 
316     unittest
317     {
318         WideUInt num1, num2;
319         num1.payload[1] = 1;
320         num2.payload[2] = 1;
321         test(num1 < num2);
322         test(num2 > num1);
323     }
324 
325     /***************************************************************************
326 
327         Increment current value by one fitting in uint
328 
329         Params:
330             rhs = value to increment with. Limited to `uint` for now to simplify
331                 internal arithmetic.
332 
333         Throws:
334             WideUIntRangeException if this WideUInt value range was to be
335             exceeded
336 
337     ***************************************************************************/
338 
339     public void opAddAssign ( uint rhs )
340     {
341         if (add((&this).payload[0], rhs))
342             enforce(.wideint_exception, (&this).checkAndInc(1));
343     }
344 
345     unittest
346     {
347         WideUInt num = 0;
348         for (auto i = 0; i < 1000; ++i)
349             num += (1 << 31);
350         test!("==")(num.payload[0], 0);
351         test!("==")(num.payload[1], 500);
352     }
353 
354     /***************************************************************************
355 
356         Mutates current number in-place, dividing it by `rhs` and calculating
357         remainder.
358 
359         Params:
360             rhs = number to divide by
361 
362         Returns:
363             remainder of division
364 
365     ***************************************************************************/
366 
367     public uint divideBy ( uint rhs )
368     out (remainder)
369     {
370         assert(remainder < rhs);
371     }
372     body
373     {
374         ulong remainder = 0;
375 
376         for (ptrdiff_t idx = (&this).payload.length - 1; idx >= 0; --idx)
377         {
378             remainder = (remainder << 32) + (&this).payload[idx];
379             ulong result = remainder / rhs;
380             remainder -= rhs * result;
381             verify(result <= uint.max);
382             (&this).payload[idx] = cast(uint) result;
383         }
384 
385         verify(remainder <= uint.max);
386         return cast(uint) remainder;
387     }
388 
389     unittest
390     {
391         WideUInt num = 56 * 47;
392         test!("==")(0, num.divideBy(47));
393         test!("==")(num.payload[0], 56);
394 
395         num.payload[0] = 1337;
396         num.payload[1] = 100;
397         test!("==")(1337, num.divideBy(1 << 31));
398         test!("==")(num.payload[0], 200);
399     }
400 
401     /***************************************************************************
402 
403         Mutates this number in place multiplying it by a regular integer
404 
405         Params:
406             rhs = value to multiply by
407 
408         Throws:
409             WideUIntRangeException if resulting value can't fit in current
410             WideUInt value range
411 
412     ***************************************************************************/
413 
414     public void multiplyBy ( uint rhs )
415     {
416         ulong overflow = 0;
417 
418         for (size_t i = 0; i < (&this).payload.length; ++i)
419         {
420             overflow += cast(ulong)((&this).payload[i]) * rhs;
421             (&this).payload[i] = cast(uint) (overflow & uint.max);
422             overflow >>= 32;
423         }
424 
425         enforce(.wideint_exception, overflow == 0);
426     }
427 
428     unittest
429     {
430         WideUInt num = 1_000_000_000;
431         num.multiplyBy(1_000_000_000);
432         test!("==")(num.toString(), "1000000000000000000");
433     }
434 
435     /***************************************************************************
436 
437         Returns:
438             Double precision floating point value which is nearest to this
439             number.  Uses the same semantics as conversion from ulong to double.
440 
441     ***************************************************************************/
442 
443     public double toDouble ( )
444     {
445         // find most significant word with non-zero value
446         int idx = (&this).payload.length-1;
447         while ((&this).payload[idx] == 0 && idx > 0)
448             --idx;
449 
450         // if stored value <= ulong.max, just use plain cast
451         if (idx < 2)
452         {
453             ulong value = (&this).payload[1];
454             value <<= 32;
455             value |= (&this).payload[0];
456             return cast(double) value;
457         }
458 
459         // else calculate floating point value from 3 most significant words
460         double MAXWORD = pow(2.0, 32.0);
461         return (((&this).payload[idx] * MAXWORD + (&this).payload[idx-1])
462                 * MAXWORD + (&this).payload[idx-2])
463             * ldexp(1.0, (idx-2)*32);
464     }
465 
466     unittest
467     {
468         WideUInt num = 123456789;
469         test!("is")(num.toDouble(), 123_456_789.0);
470 
471         for (int i = 0; i < 3; ++i)
472             num.multiplyBy(1_000_000_000);
473 
474         test(feqrel(num.toDouble(), 123_456_789e+27) == double.mant_dig);
475     }
476 
477     /***************************************************************************
478 
479         Helper utility to increment word under index `idx`, calling itself
480         recursively for following words if wraparound happens.
481 
482         Params:
483             idx = index of word in the internal payload to increment by 1
484 
485         Returns:
486             'false' if whole WideUInt has overflown (== error)
487 
488     ***************************************************************************/
489 
490     private bool checkAndInc ( size_t idx )
491     {
492         if (idx >= N)
493             return false;
494 
495         uint after = ++(&this).payload[idx];
496         if (after == 0)
497             return (&this).checkAndInc(idx+1);
498 
499         return true;
500     }
501 
502     unittest
503     {
504         WideUInt num = uint.max;
505         test(num.checkAndInc(0));
506         test(num.payload[0] == 0);
507         test(num.payload[1] == 1);
508 
509         num.payload[$-1] = uint.max;
510         test(!num.checkAndInc(num.payload.length - 1));
511     }
512 
513     /***************************************************************************
514 
515         Trivial helper to initialize to least significant words of the number
516         from ulong.
517 
518     ***************************************************************************/
519 
520     private void assign ( ulong value )
521     {
522         (&this).payload[] = 0;
523         (&this).payload[0] = cast(uint) value;
524         (&this).payload[1] = cast(uint) (value >> 32);
525     }
526 
527     /***************************************************************************
528 
529         Helper utility that adds two uint integers checking for wraparound
530 
531         Params:
532             a = reference to integer to increment
533             b = number to add
534 
535         Returns:
536             'true' if wraparound/overflow has happenned, 'false' otherwise
537 
538     ***************************************************************************/
539 
540     static private bool add ( ref uint a, uint b )
541     {
542         bool flag;
543 
544         version (D_InlineAsm_X86_64)
545         {
546             asm
547             {
548                 add   dword ptr [RSI], EDI;
549                 setb  flag;
550             }
551         }
552         else
553         {
554             uint result = a + b;
555             flag = result < a;
556             a = result;
557         }
558 
559         return flag;
560     }
561 
562     unittest
563     {
564         uint a = uint.max;
565         test( add(a, 1));
566         test!("==")(a, 0);
567 
568         a = 1000;
569         test(!add(a, 1337));
570         test!("==")(a, 2337UL);
571 
572         a = 0;
573         test(!add(a, uint.max));
574         test!("==")(a, uint.max);
575     }
576 }
577 
578 ///
579 unittest
580 {
581     WideUInt!(4) counter;
582     counter = 10000000000000000000U;
583     counter.multiplyBy(10);
584     ++counter;
585     test!("==")(counter.toString(), "100000000000000000001");
586 }
587 
588 unittest
589 {
590     WideUInt!(4) num = ulong.max;
591     test!("==")(num.toString(), "18446744073709551615");
592     num.multiplyBy(2);
593     test!("==")(num.toString(), "36893488147419103230");
594     num.divideBy(2);
595     test!("==")(num.toString(), "18446744073709551615");
596 
597     num = 0;
598     num.payload[2] = 1; // ulong.max + 1
599     test!("==")(num.toString(), "18446744073709551616");
600 }
601 
602 /*******************************************************************************
603 
604     Exception of this class is thrown when WideUInt overflow is about to happen
605 
606 *******************************************************************************/
607 
608 public class WideUIntRangeException : Exception
609 {
610     import ocean.core.Exception : DefaultExceptionCtor;
611     mixin DefaultExceptionCtor;
612 }
613 
614 // shared between different WideUInt!(N) instances
615 private static WideUIntRangeException wideint_exception;
616 
617 static this ( )
618 {
619     wideint_exception = new WideUIntRangeException(
620         "Operation would result in WideUInt overflow");
621 }