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