1 /*
2     Adapted from the specification of the General Decimal Arithmetic.
3 
4     This implementation is written for the D Programming Language
5     by Jack Stouffer and is licensed under the Boost Software License 1.0.
6 */
7 module stdxdecimal;
8 
9 import std.stdio;
10 import std.range.primitives;
11 import std.traits;
12 
13 /**
14  * Behavior is defined by `Hook`. Number of significant digits is limited by 
15  * `Hook.precision`.
16  * 
17  * Spec: http://speleotrove.com/decimal/decarith.html
18  */
19 struct Decimal(Hook = Abort)
20 {
21     import std.experimental.allocator.common : stateSize;
22 
23     static assert(
24         hasMember!(Hook, "precision") && is(typeof(Hook.precision) : uint),
25         "The Hook must have a defined precision that's convertible to uint"
26     );
27     static assert(
28         isEnum!(Hook.precision),
29         "Hook.precision must be readable at compile-time"
30     );
31     static assert(
32         hasMember!(Hook, "roundingMode") && is(typeof(Hook.roundingMode) == Rounding),
33         "The Hook must have a defined Rounding"
34     );
35     static assert(
36         isEnum!(Hook.roundingMode),
37         "Hook.roundingMode must be readable at compile-time"
38     );
39     static assert(
40         hook.precision > 1,
41         "Hook.precision is too small (must be at least 2)"
42     );
43 
44 package:
45     // 1 indicates that the number is negative or is the negative zero
46     // and 0 indicates that the number is zero or positive.
47     bool sign;
48     // quiet NaN
49     bool qNaN;
50     // signaling NaN
51     bool sNaN;
52     // Infinite
53     bool inf;
54 
55     // actual value of decimal given as (–1)^^sign × coefficient × 10^^exponent
56     static if (useBigInt)
57     {
58         import std.BigInt : BigInt;
59         BigInt coefficient;
60     }
61     else
62     {
63         ulong coefficient;
64     }
65     long exponent;
66 
67     enum hasClampedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onClamped(d); });
68     enum hasRoundedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onRounded(d); });
69     enum hasInexactMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInexact(d); });
70     enum hasDivisionByZeroMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onDivisionByZero(d); });
71     enum hasInvalidOperationMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInvalidOperation(d); });
72     enum hasOverflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onOverflow(d); });
73     enum hasSubnormalMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onSubnormal(d); });
74     enum hasUnderflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onUnderflow(d); });
75 
76     // The cut off point is set at 9 because 999_999_999 ^^ 2
77     // can still fit in a ulong
78     enum useBigInt = Hook.precision > 9;
79 
80     /*
81         rounds the coefficient via `Hook`s rounding mode.
82 
83         Rounded is always set if the coefficient is changed
84 
85         Inexact is set if the rounded digits were non-zero
86      */
87     auto round()
88     {
89         auto digits = numberOfDigits(coefficient);
90 
91         if (digits <= hook.precision)
92             return;
93 
94         typeof(coefficient) lastDigit;
95 
96         static if (hook.roundingMode == Rounding.Down)
97         {
98             while (digits > hook.precision)
99             {
100                 if (!inexact)
101                     lastDigit = coefficient % 10;
102                 coefficient /= 10;
103                 --digits;
104                 ++exponent;
105 
106                 if (!inexact && lastDigit != 0)
107                     inexact = true;
108             }
109         }
110         else static if (hook.roundingMode == Rounding.Up)
111         {
112             while (digits > hook.precision)
113             {
114                 if (!inexact)
115                     lastDigit = coefficient % 10;
116                 coefficient /= 10;
117                 --digits;
118                 ++exponent;
119 
120                 if (!inexact && lastDigit != 0)
121                     inexact = true;
122             }
123 
124             // If all of the discarded digits are zero the result is unchanged
125             if (inexact)
126                 ++coefficient;
127         }
128         else static if (hook.roundingMode == Rounding.HalfUp)
129         {
130             while (digits > hook.precision + 1)
131             {
132                 if (!inexact)
133                     lastDigit = coefficient % 10;
134 
135                 coefficient /= 10;
136                 --digits;
137                 ++exponent;
138 
139                 if (!inexact && lastDigit != 0)
140                     inexact = true;
141             }
142 
143             lastDigit = coefficient % 10;
144             if (lastDigit != 0)
145                 inexact = true;
146             coefficient /= 10;
147             ++exponent;
148 
149             if (lastDigit >= 5)
150                 ++coefficient;
151         }
152         else
153         {
154             static assert(0, "Not implemented");
155         }
156 
157         rounded = true;
158 
159         static if (hasInexactMethod)
160             if (inexact)
161                 hook.onInexact(this);
162 
163         static if (hasRoundedMethod)
164             hook.onRounded(this);
165 
166         return;
167     }
168 
169 public:
170     /**
171      * `hook` is a member variable if it has state, or an alias for `Hook`
172      * otherwise.
173      */
174     static if (stateSize!Hook > 0)
175         Hook hook;
176     else
177         alias hook = Hook;
178 
179     /// Public flags
180     bool clamped;
181     /// ditto
182     bool divisionByZero;
183     /// ditto
184     bool inexact;
185     /// ditto
186     bool invalidOperation;
187     /// ditto
188     bool overflow;
189     /// ditto
190     bool rounded;
191     /// ditto
192     bool subnormal;
193     /// ditto
194     bool underflow;
195 
196     /**
197      * Note: Float construction less accurate that string, Use
198      * string construction if possible
199      */
200     this(T)(const T num) pure if (isNumeric!T)
201     {
202         // the behavior of conversion from built-in number types
203         // isn't covered by the spec, so we can do whatever we
204         // want here
205 
206         static if (isIntegral!T)
207         {
208             import std.math : abs;
209 
210             coefficient = abs(num);
211             sign = num >= 0 ? 0 : 1;
212         }
213         else
214         {
215             import std.math : abs, isInfinity, isNaN;
216 
217             Unqual!T val = num;
218 
219             if (isInfinity(val))
220             {
221                 inf = true;
222                 sign = val < 0 ? 0 : 1;
223                 return;
224             }
225 
226             if (isNaN(val))
227             {
228                 qNaN = true;
229                 sign = val == T.nan ? 0 : 1;
230                 return;
231             }
232 
233             sign = val >= 0 ? 0 : 1;
234             val = abs(val);
235 
236             // while the number still has a fractional part, multiply by 10,
237             // counting each time until no fractional part
238             Unqual!T fraction = val - (cast(long) val);
239             while (fraction > 0)
240             {
241                 --exponent;
242                 val *= 10;
243                 fraction = val - (cast(long) val);
244             }
245 
246             coefficient = cast(size_t) val;
247         }
248 
249         round();
250     }
251 
252     /**
253      * implements spec to-number
254      */
255     this(S)(S str) if (isForwardRange!S && isSomeChar!(ElementEncodingType!S) && !isInfinite!S)
256     {
257         import std.algorithm.comparison : among, equal;
258         import std.algorithm.iteration : filter, map;
259         import std.algorithm.searching : startsWith;
260         import std.ascii : isDigit, toLower;
261         import std.utf : byChar, byCodeUnit;
262 
263         static bool asciiCmp(S1)(S1 a, string b)
264         {
265             return a.map!toLower.equal(b.byChar.map!toLower);
266         }
267 
268         // valid characters in non-special number strings
269         static bool charCheck(dchar a)
270         {
271             return isDigit(a) || a == 'E' || a == 'e' || a == '-' || a == '+' || a == '.';
272         }
273 
274         static if (isSomeString!S)
275             auto codeUnits = str.byCodeUnit;
276         else
277             alias codeUnits = str;
278 
279         if (codeUnits.empty)
280         {
281             qNaN = true;
282             return;
283         }
284 
285         // all variables are declared here to make gotos compile
286         immutable frontResult = codeUnits.front;
287         bool sawDecimal = false;
288         bool sawExponent = false;
289         bool sawExponentSign = false;
290         byte exponentSign;
291         long sciExponent = 0;
292 
293         if (frontResult == '+')
294         {
295             codeUnits.popFront;
296         }
297         else if (frontResult == '-')
298         {
299             sign = 1;
300             codeUnits.popFront;
301         }
302 
303         if (codeUnits.empty)
304         {
305             sign = 0;
306             goto Lerr;
307         }
308 
309         if (codeUnits.among!((a, b) => asciiCmp(a.save, b))
310                ("inf", "infinity"))
311         {
312             inf = true;
313             return;
314         }
315 
316         // having numbers after nan is valid in the spec
317         if (codeUnits.save.map!toLower.startsWith("qnan".byChar) ||
318             codeUnits.save.map!toLower.startsWith("nan".byChar))
319         {
320             qNaN = true;
321             return;
322         }
323 
324         if (codeUnits.save.map!toLower.startsWith("snan".byChar))
325         {
326             sNaN = true;
327             return;
328         }
329 
330         for (; !codeUnits.empty; codeUnits.popFront)
331         {
332             auto digit = codeUnits.front;
333 
334             if (!charCheck(digit))
335                 goto Lerr;
336 
337             if (isDigit(digit))
338             {
339                 if (!sawExponent)
340                 {
341                     coefficient *= 10;
342                     coefficient += cast(uint) (digit - '0');
343                 }
344 
345                 if (sawDecimal && !sawExponent)
346                     exponent--;
347 
348                 if (sawExponent)
349                 {
350                     while (!codeUnits.empty)
351                     {
352                         if (!isDigit(codeUnits.front))
353                             goto Lerr;
354 
355                         sciExponent += cast(uint) (codeUnits.front - '0');
356                         if (!codeUnits.empty)
357                         {
358                             codeUnits.popFront;
359                             if (!codeUnits.empty)
360                                 sciExponent *= 10;
361                         }
362                     }
363 
364                     if (sawExponentSign && exponentSign == -1)
365                         sciExponent *= -1;
366 
367                     exponent += sciExponent;
368 
369                     if (codeUnits.empty)
370                     {
371                         round();
372                         return;
373                     }
374                 }
375             }
376 
377             if (digit == '+' || digit == '-')
378             {
379                 // already have exponent sign, bad input so cancel out
380                 if (sawExponentSign)
381                     goto Lerr;
382 
383                 if (sawExponent)
384                 {
385                     if (digit == '-')
386                         exponentSign = -1;
387                     sawExponentSign = true;
388                 }
389                 else
390                 { // no exponent yet, bad input so cancel out
391                     goto Lerr;
392                 }
393             }
394 
395             if (digit == '.')
396             {
397                 // already have decimal, bad input so cancel out
398                 if (sawDecimal)
399                     goto Lerr;
400 
401                 sawDecimal = true;
402             }
403 
404             if (digit.toLower == 'e')
405             {
406                 // already have exponent, bad input so cancel out
407                 if (sawExponent)
408                     goto Lerr;
409 
410                 sawExponent = true;
411             }
412         }
413 
414         round();
415         return;
416 
417         Lerr:
418             qNaN = true;
419             coefficient = 0;
420             exponent = 0;
421 
422             invalidOperation = true;
423             static if (hasInvalidOperationMethod)
424                 hook.onInvalidOperation(this);
425 
426             return;
427     }
428 
429     /**
430      * The result has the hook of the left hand side. Invalid operations
431      * call `onInvalidOperation` on the `hook` on the result and set the
432      * result's flag to `true`. Does not effect the left hand side of the
433      * operation.
434      *
435      * When the right hand side is a built-in numeric type, the default
436      * hook `Abort` is used for its decimal representation.
437      */
438     auto opBinary(string op, T)(T rhs) const if (op == "+" || op == "-")
439     {
440         static if (isNumeric!T)
441         {
442             auto temp = decimal(rhs);
443             return mixin("this " ~ op ~ " temp");
444         }
445         else static if (op == "+" || op == "-")
446         {
447             import std.algorithm.comparison : min;
448             import std.math : abs;
449 
450             static if (op == "-")
451                 rhs.sign = rhs.sign == 0 ? 1 : 0;
452 
453             Decimal!(hook) res;
454 
455             if (sNaN || rhs.sNaN)
456             {
457                 if (sign == 0)
458                 {
459                     res.sNaN = true;
460                 }
461                 else
462                 {
463                     res.sNaN = true;
464                     res.sign = 1;
465                 }
466                 
467                 res.invalidOperation = true;
468                 static if (hasInvalidOperationMethod)
469                     res.hook.onInvalidOperation(this);
470                 return res;
471             }
472 
473             if (qNaN || rhs.qNaN)
474             {
475                 if (sign == 1)
476                     res.sign = 1;
477 
478                 res.qNaN = true;
479                 return res;
480             }
481 
482             if (inf && rhs.inf)
483             {
484                 if (sign == 1 && rhs.sign == 1)
485                 {
486                     res.sign = 1;
487                     res.inf = true;
488                     return res;
489                 }
490 
491                 if (sign == 0 && rhs.sign == 0)
492                 {
493                     res.inf = true;
494                     return res;
495                 }
496 
497                 // -Inf + Inf makes no sense
498                 res.qNaN = true;
499                 res.invalidOperation = true;
500                 return res;
501             }
502 
503             if (inf)
504             {
505                 res.inf = true;
506                 res.sign = sign;
507                 return res;
508             }
509 
510             if (rhs.inf)
511             {
512                 res.inf = true;
513                 res.sign = rhs.sign;
514                 return res;
515             }
516 
517             res = Decimal!(hook)(0);
518             Unqual!(typeof(coefficient)) alignedCoefficient = coefficient;
519             Unqual!(typeof(rhs.coefficient)) rhsAlignedCoefficient = rhs.coefficient;
520 
521             if (exponent != rhs.exponent)
522             {
523                 long diff;
524                 bool overflow;
525 
526                 if (exponent > rhs.exponent)
527                 {
528                     diff = abs(exponent - rhs.exponent);
529 
530                     static if (useBigInt)
531                     {
532                         alignedCoefficient *= 10 ^^ diff;
533                     }
534                     else
535                     {
536                         import core.checkedint : mulu;
537                         alignedCoefficient = mulu(alignedCoefficient, 10 ^^ diff, overflow);
538                         // the Overflow condition is only raised if exponents are incorrect,
539                         // has nothing to do with coefficients, so abort
540                         if (overflow)
541                             assert(0, "Arithmetic operation failed due to coefficient overflow");
542                     }
543                 }
544                 else
545                 {
546                     diff = abs(rhs.exponent - exponent);
547 
548                     static if (useBigInt)
549                     {
550                         rhsAlignedCoefficient *= 10 ^^ diff;
551                     }
552                     else
553                     {
554                         import core.checkedint : mulu;
555                         rhsAlignedCoefficient = mulu(rhsAlignedCoefficient, 10 ^^ diff, overflow);
556                         if (overflow)
557                             assert(0, "Arithmetic operation failed due to coefficient overflow");      
558                     }
559                 }
560             }
561 
562             // If the signs of the operands differ then the smaller aligned coefficient
563             // is subtracted from the larger; otherwise they are added.
564             if (sign == rhs.sign)
565             {
566                 if (alignedCoefficient >= rhsAlignedCoefficient)
567                     res.coefficient = alignedCoefficient + rhsAlignedCoefficient;
568                 else
569                     res.coefficient = rhsAlignedCoefficient + alignedCoefficient;
570             }
571             else
572             {
573                 if (alignedCoefficient >= rhsAlignedCoefficient)
574                     res.coefficient = alignedCoefficient - rhsAlignedCoefficient;
575                 else
576                     res.coefficient = rhsAlignedCoefficient - alignedCoefficient;
577             }
578 
579             res.exponent = min(exponent, rhs.exponent);
580 
581             if (res.coefficient != 0)
582             {
583                 // the sign of the result is the sign of the operand having
584                 // the larger absolute value.
585                 if (alignedCoefficient >= rhsAlignedCoefficient)
586                     res.sign = sign;
587                 else
588                     res.sign = rhs.sign;
589             }
590             else
591             {
592                 if (sign == 1 && rhs.sign == 1)
593                     res.sign = 1;
594 
595                 static if (hook.roundingMode == Rounding.Floor)
596                     if (sign != rhs.sign)
597                         res.sign = 1;
598             }
599 
600             res.round();
601             return res;
602         }
603         else
604         {
605             static assert(0, "Not implemented yet");
606         }
607     }
608 
609     /**
610      * The spec says that comparing `NAN`s should yield `NAN`.
611      * Unfortunately this isn't possible in D, as the return value of `opCmp` must be
612      * [`-1`, `1`].
613      *
614      * Further, in D, the NaN values of floating point types always return `false` in
615      * any comparison. But, this makes sorting an array with NaN values impossible.
616      *
617      * So, `-INF` is less than all numbers, `-NAN` is greater than `-INF` but
618      * less than all other numbers, `NAN` is greater than `-NAN` but less than all other
619      * numbers and inf is greater than all numbers. `-NAN` and `NAN` are equal to
620      * themselves. 
621      *
622      * Signaling NAN is an invalid operation, and will trigger the appropriate hook
623      * method and always yield `-1`.
624      */
625     int opCmp(T)(const T d) // For some reason isInstanceOf refuses to work here
626     {
627         static if (!isNumeric!T)
628         {
629             if (sNaN || d.sNaN)
630             {
631                 invalidOperation = true;
632                 static if (hasInvalidOperationMethod)
633                     hook.onInvalidOperation(this);
634                 return -1;
635             }
636 
637             if (inf && sign == 1 && (inf != d.inf || sign != d.sign))
638                 return -1;
639             if (inf && sign == 0 && (inf != d.inf || sign != d.sign))
640                 return 1;
641             if (inf && d.inf && sign == d.sign)
642                 return 0;
643 
644             if (qNaN && d.qNaN)
645             {
646                 if (sign == d.sign)
647                     return 0;
648                 if (sign == 1)
649                     return -1;
650 
651                 return 1;
652             }
653 
654             if (qNaN && !d.qNaN)
655                 return -1;
656             if (!qNaN && d.qNaN)
657                 return 1;
658 
659             Decimal!(Hook) lhs;
660             Decimal!(d.hook) rhs;
661 
662             // If the signs of the operands differ, a value representing each
663             // operand (’-1’ if the operand is less than zero, ’0’ if the
664             // operand is zero or negative zero, or ’1’ if the operand is
665             // greater than zero) is used in place of that operand for the
666             // comparison instead of the actual operand.
667             if (sign != d.sign)
668             {
669                 if (sign == 0)
670                 {
671                     if (coefficient > 0)
672                         lhs.coefficient = 1;
673                 }
674                 else
675                 {
676                     if (coefficient > 0)
677                     {
678                         lhs.sign = 1;
679                         lhs.coefficient = 1;
680                     }
681                 }
682 
683                 if (d.sign == 0)
684                 {
685                     if (d.coefficient > 0)
686                         rhs.coefficient = 1;
687                 }
688                 else
689                 {
690                     if (d.coefficient > 0)
691                     {
692                         rhs.sign = 1;
693                         rhs.coefficient = 1;
694                     }
695                 }
696             }
697             else
698             {
699                 lhs.sign = sign;
700                 lhs.coefficient = coefficient;
701                 lhs.exponent = exponent;
702                 rhs.sign = d.sign;
703                 rhs.coefficient = d.coefficient;
704                 rhs.exponent = d.exponent;
705             }
706 
707             auto res = lhs - rhs;
708 
709             if (res.sign == 0)
710             {
711                 if (res.coefficient == 0)
712                     return 0;
713                 else
714                     return 1;
715             }
716 
717             return -1;
718         }
719         else
720         {
721             return this.opCmp(d.decimal);
722         }
723     }
724 
725     ///
726     bool opEquals(T)(const T d)
727     {
728         return this.opCmp(d) == 0;
729     }
730 
731     ///
732     alias toString = toDecimalString;
733 
734     /**
735      * Decimal strings
736      *
737      * Special Values:
738      *     Quiet Not A Number = `NaN`
739      *     Signal Not A Number = `sNaN`
740      *     Infinite = `Infinity`
741      *
742      *     If negative, then all of above have `-` pre-pended
743      */
744     auto toDecimalString() const
745     {
746         import std.array : appender;
747         import std.math : abs;
748 
749         auto app = appender!string();
750         if (exponent > 10 || exponent < -10)
751             app.reserve(abs(exponent) + hook.precision);
752         toDecimalString(app);
753         return app.data;
754     }
755 
756     /// ditto
757     void toDecimalString(Writer)(auto ref Writer w) const if (isOutputRange!(Writer, char))
758     {
759         import std.math : pow;
760         import std.range : repeat;
761 
762         if (sign == 1)
763             w.put('-');
764 
765         if (inf)
766         {
767             w.put("Infinity");
768             return;
769         }
770 
771         if (qNaN)
772         {
773             w.put("NaN");
774             return;
775         }
776 
777         if (sNaN)
778         {
779             w.put("sNaN");
780             return;
781         }
782 
783         static if (useBigInt)
784         {
785             import std.bigint : toDecimalString;
786             auto temp = coefficient.toDecimalString;
787         }
788         else
789         {
790             import std.conv : toChars;
791             auto temp = coefficient.toChars;
792         }
793 
794         auto decimalPlace = exponent * -1;
795 
796         if (decimalPlace > 0)
797         {
798             if (temp.length - decimalPlace == 0)
799             {
800                 w.put("0.");
801                 w.put(temp);
802                 return;
803             }
804 
805             if ((cast(long) temp.length) - decimalPlace > 0)
806             {
807                 w.put(temp[0 .. $ - decimalPlace]);
808                 w.put('.');
809                 w.put(temp[$ - decimalPlace .. $]);
810                 return;
811             }
812 
813             if ((cast(long) temp.length) - decimalPlace < 0)
814             {
815                 w.put("0.");
816                 w.put('0'.repeat(decimalPlace - temp.length));
817                 w.put(temp);
818                 return;
819             }
820         }
821 
822         if (decimalPlace < 0)
823         {
824             w.put(temp);
825             w.put('0'.repeat(exponent));
826             return;
827         }
828 
829         w.put(temp);
830     }
831 }
832 
833 // string construction
834 @safe pure nothrow
835 unittest
836 {
837     static struct Test
838     {
839         string val;
840         ubyte sign;
841         int coefficient;
842         long exponent;
843     }
844 
845     static struct SpecialTest
846     {
847         string val;
848         ubyte sign;
849         bool qNaN;
850         bool sNaN;
851         bool inf;
852         bool invalid;
853     }
854 
855     auto nonspecialTestValues = [
856         Test("0", 0, 0, 0),
857         Test("+0", 0, 0, 0),
858         Test("-0", 1, 0, 0),
859         Test("1.0", 0, 10, -1),
860         Test("0E+7", 0, 0, 7),
861         Test("-0E-7", 1, 0, -7),
862         Test("1.23E3", 0, 123, 1),
863         Test("0001.0000", 0, 10000, -4),
864         Test("-10.0004", 1, 100004, -4),
865         Test("+15", 0, 15, 0),
866         Test("-15", 1, 15, 0),
867         Test("1234.5E-4", 0, 12345, -5),
868         Test("30.5E10", 0, 305, 9) 
869     ];
870 
871     auto specialTestValues = [
872         SpecialTest("NaN", 0, true, false, false),
873         SpecialTest("+nan", 0, true, false, false),
874         SpecialTest("-nan", 1, true, false, false),
875         SpecialTest("-NAN", 1, true, false, false),
876         SpecialTest("Infinite", 0, true, false, false, true),
877         SpecialTest("infinity", 0, false, false, true),
878         SpecialTest("-INFINITY", 1, false, false, true),
879         SpecialTest("inf", 0, false, false, true),
880         SpecialTest("-inf", 1, false, false, true),
881         SpecialTest("snan", 0, false, true, false),
882         SpecialTest("-snan", 1, false, true, false),
883         SpecialTest("Jack", 0, true, false, false, true),
884         SpecialTest("+", 0, true, false, false, true),
885         SpecialTest("-", 0, true, false, false, true),
886         SpecialTest("nan0123", 0, true, false, false),
887         SpecialTest("-nan0123", 1, true, false, false),
888         SpecialTest("snan0123", 0, false, true, false),
889         SpecialTest("12+3", 0, true, false, false, true),
890         SpecialTest("1.2.3", 0, true, false, false, true),
891         SpecialTest("123.0E+7E+7", 0, true, false, false, true),
892     ];
893 
894     foreach (el; nonspecialTestValues)
895     {
896         auto d = Decimal!()(el.val);
897         assert(d.coefficient == el.coefficient);
898         assert(d.sign == el.sign);
899         assert(d.exponent == el.exponent);
900     }
901 
902     foreach (el; specialTestValues)
903     {
904         auto d = Decimal!(NoOp)(el.val);
905         assert(d.qNaN == el.qNaN);
906         assert(d.sNaN == el.sNaN);
907         assert(d.inf == el.inf);
908         assert(d.invalidOperation == el.invalid);
909     }
910 }
911 
912 // range construction
913 @system pure
914 unittest
915 {
916     import std.internal.test.dummyrange;
917     auto r1 = new ReferenceForwardRange!dchar("123.456");
918     auto d1 = Decimal!()(r1);
919     assert(d1.coefficient == 123456);
920     assert(d1.sign == 0);
921     assert(d1.exponent == -3);
922 
923     auto r2 = new ReferenceForwardRange!dchar("-0.00004");
924     auto d2 = Decimal!()(r2);
925     assert(d2.coefficient == 4);
926     assert(d2.sign == 1);
927     assert(d2.exponent == -5);
928 }
929 
930 // int construction
931 @safe pure nothrow
932 unittest
933 {
934     static struct Test
935     {
936         long val;
937         ubyte sign;
938         long coefficient;
939     }
940 
941     auto testValues = [
942         Test(10, 0, 10),
943         Test(-10, 1, 10),
944         Test(-1000000, 1, 1000000),
945         Test(-147_483_648, 1, 147_483_648),
946     ];
947 
948     foreach (el; testValues)
949     {
950         auto d = Decimal!()(el.val);
951         assert(d.coefficient == el.coefficient);
952         assert(d.sign == el.sign);
953     }
954 }
955 
956 // float construction
957 unittest
958 {
959     static struct Test
960     {
961         double val;
962         ubyte sign;
963         int coefficient;
964         long exponent;
965     }
966 
967     static struct SpecialTest
968     {
969         double val;
970         ubyte sign;
971         bool qNaN;
972         bool sNaN;
973         bool inf;
974     }
975 
976     auto nonspecialTestValues = [
977         Test(0.02, 0, 2, -2),
978         Test(0.00002, 0, 2, -5),
979         Test(1.02, 0, 102, -2),
980         Test(200.0, 0, 200, 0),
981         Test(1234.5678, 0, 12345678, -4),
982         Test(-1234.5678, 1, 12345678, -4),
983         Test(-1234, 1, 1234, 0),
984     ];
985 
986     auto specialTestValues = [
987         SpecialTest(float.nan, 0, true, false, false),
988         SpecialTest(-float.nan, 1, true, false, false),
989         SpecialTest(float.infinity, 0, false, false, true),
990         SpecialTest(-float.infinity, 1, false, false, true),
991     ];
992 
993     foreach (el; nonspecialTestValues)
994     {
995         auto d = Decimal!()(el.val);
996         assert(d.coefficient == el.coefficient);
997         assert(d.sign == el.sign);
998         assert(d.exponent == el.exponent);
999     }
1000 
1001     foreach (el; specialTestValues)
1002     {
1003         auto d = Decimal!()(el.val);
1004         assert(d.qNaN == el.qNaN);
1005         assert(d.sNaN == el.sNaN);
1006         assert(d.inf == el.inf);
1007     }
1008 }
1009 
1010 // addition and subtraction
1011 @system
1012 unittest
1013 {
1014     static struct Test
1015     {
1016         string val1;
1017         string val2;
1018         string expected;
1019         bool invalidOperation;
1020     }
1021 
1022     auto testPlusValues = [
1023         Test("-0", "-0", "-0"),
1024         Test("-0", "0", "0"),
1025         Test("1", "2", "3"),
1026         Test("-5", "-3", "-8"),
1027         Test("5.75", "3.3", "9.05"),
1028         Test("1.23456789", "1.00000000", "2.23456789"),
1029         Test("10E5", "10E4", "1100000"),
1030         Test("0.9998", "0.0000", "0.9998"),
1031         Test("1", "0.0001", "1.0001"),
1032         Test("1", "0.00", "1.00"),
1033         Test("123.00", "3000.00", "3123.00"),
1034         Test("123.00", "3000.00", "3123.00"),
1035         Test("sNaN", "sNaN", "sNaN", true),
1036         Test("-sNaN", "sNaN", "-sNaN", true),
1037         Test("NaN", "sNaN", "sNaN", true),
1038         Test("sNaN", "1000", "sNaN", true),
1039         Test("1000", "sNaN", "sNaN", true),
1040         Test("1000", "NaN", "NaN"),
1041         Test("NaN", "NaN", "NaN"),
1042         Test("-NaN", "NaN", "-NaN"),
1043         Test("NaN", "Inf", "NaN"),
1044         Test("-NaN", "Inf", "-NaN"),
1045         Test("-Inf", "-Inf", "-Infinity"),
1046         Test("-Inf", "Inf", "NaN", true),
1047         Test("Inf", "-Inf", "NaN", true),
1048         Test("-Inf", "-1000", "-Infinity"),
1049         Test("-Inf", "1000", "-Infinity"),
1050         Test("Inf", "-1000", "Infinity"),
1051         Test("Inf", "1000", "Infinity")
1052     ];
1053 
1054     auto testMinusValues = [
1055         Test("-0", "0", "-0"),
1056         Test("0", "-0", "0"),
1057         Test("0", "0", "0"),
1058         Test("1.3", "0.3", "1.0"),
1059         Test("1.3", "2.07", "-0.77"),
1060         Test("1.25", "1.25", "0.00"),
1061         Test("3", "-3.0", "6.0"),
1062         Test("1.23456789", "1.00000000", "0.23456789"),
1063         Test("10.2345679", "10.2345675", "0.0000004"),
1064         Test("0.999999999", "1", "-0.000000001"),
1065         Test("2.000E-3", "1.00200", "-1.000000"),
1066         Test("-Inf", "Inf", "-Infinity"),
1067         Test("-Inf", "1000", "-Infinity"),
1068         Test("1000", "-Inf", "Infinity"),
1069         Test("NaN", "Inf", "NaN"),
1070         Test("Inf", "NaN", "NaN"),
1071         Test("NaN", "NaN", "NaN"),
1072         Test("-NaN", "NaN", "-NaN"),
1073         Test("sNaN", "0", "sNaN", true),
1074         Test("sNaN", "-Inf", "sNaN", true),
1075         Test("sNaN", "NaN", "sNaN", true),
1076         Test("1000", "sNaN", "sNaN", true),
1077         Test("-sNaN", "sNaN", "-sNaN", true),
1078         Test("Inf", "Inf", "NaN", true),
1079         Test("-Inf", "-Inf", "NaN", true),
1080     ];
1081 
1082     foreach (el; testPlusValues)
1083     {
1084         auto v1 = decimal!(NoOp)(el.val1);
1085         auto v2 = decimal(el.val2);
1086         auto res = v1 + v2;
1087         assert(res.toString() == el.expected);
1088         assert(res.invalidOperation == el.invalidOperation);
1089     }
1090 
1091     foreach (el; testMinusValues)
1092     {
1093         auto v1 = decimal!(NoOp)(el.val1);
1094         auto v2 = decimal(el.val2);
1095         auto res = v1 - v2;
1096         assert(res.toString() == el.expected);
1097         assert(res.invalidOperation == el.invalidOperation);
1098     }
1099 
1100     // check that float and int compile
1101     assert(decimal("2.22") + 0.01 == decimal("2.23"));
1102     assert(decimal("2.22") + 1 == decimal("3.22"));
1103 
1104     static struct CustomHook
1105     {
1106         enum Rounding roundingMode = Rounding.HalfUp;
1107         enum uint precision = 3;
1108     }
1109 
1110     // rounding test on addition
1111     auto d1 = decimal!(CustomHook)("0.999E-2");
1112     auto d2 = decimal!(CustomHook)("0.1E-2");
1113     auto v = d1 + d2;
1114     assert(v.toString == "0.0110");
1115     assert(v.inexact);
1116     assert(v.rounded);
1117 
1118     // higher precision tests
1119     auto d3 = decimal!(HighPrecision)("10000e+9");
1120     auto d4 = decimal!(HighPrecision)("7");
1121     auto v2 = d3 - d4;
1122     assert(v2.toString() == "9999999999993");
1123 
1124     auto d5 = decimal!(HighPrecision)("1e-50");
1125     auto d6 = decimal!(HighPrecision)("4e-50");
1126     auto v3 = d5 + d6;
1127     assert(v3.toString() == "0.00000000000000000000000000000000000000000000000005");
1128 }
1129 
1130 // cmp and equals
1131 unittest
1132 {
1133     static struct Test
1134     {
1135         string val1;
1136         string val2;
1137         int expected;
1138         bool invalidOperation;
1139     }
1140 
1141     auto testValues = [
1142         Test("inf", "0", 1),
1143         Test("-inf", "0", -1),
1144         Test("-inf", "-inf", 0),
1145         Test("inf", "-inf", 1),
1146         Test("-inf", "inf", -1),
1147         Test("NaN", "1000", -1),
1148         Test("-NaN", "1000", -1),
1149         Test("1000", "NAN", 1),
1150         Test("1000", "-NAN", 1),
1151         Test("NaN", "inf", -1),
1152         Test("-NaN", "inf", -1),
1153         Test("-NaN", "NaN", -1),
1154         Test("NaN", "-NaN", 1),
1155         Test("-NaN", "-NaN", 0),
1156         Test("NaN", "NaN", 0),
1157         Test("sNaN", "NaN", -1, true),
1158         Test("sNaN", "-Inf", -1, true),
1159         Test("sNaN", "100", -1, true),
1160         Test("0", "-0", 0),
1161         Test("2.1", "3", -1),
1162         Test("2.1", "2.1", 0),
1163         Test("2.1", "2.10", 0),
1164         Test("3", "2.1", 1),
1165         Test("2.1", "-3", 1),
1166         Test("-3", "2.1", -1),
1167         Test("00", "00", 0),
1168         Test("70E-1", "7", 0),
1169         Test("8", "0.7E+1", 1),
1170         Test("-8.0", "7.0", -1),
1171         Test("80E-1", "-9", 1),
1172         Test("1E-15", "1", -1),
1173         Test("-0E2", "0", 0),
1174         Test("-8", "-70E-1", -1),
1175         Test("-12.1234", "-12.000000000", -1),
1176     ];
1177 
1178     foreach (el; testValues)
1179     {
1180         auto v1 = decimal!(NoOp)(el.val1);
1181         auto v2 = decimal!(NoOp)(el.val2);
1182         assert(v1.opCmp(v2) == el.expected);
1183         assert(v1.invalidOperation == el.invalidOperation);
1184     }
1185 
1186     // make sure equals compiles, already covered behavior in
1187     // cmp tests
1188     assert(decimal("19.9999") != decimal("21.222222"));
1189     assert(decimal("22.000") == decimal("22"));
1190     assert(decimal("22.000") == 22);
1191     assert(decimal("22.2") == 22.2);
1192 }
1193 
1194 // to string
1195 unittest
1196 {
1197     auto t = Decimal!()();
1198     t.sign = 0;
1199     t.coefficient = 2_708;
1200     t.exponent = -2;
1201     assert(t.toString() == "27.08");
1202 
1203     auto t2 = Decimal!()();
1204     t2.sign = 1;
1205     t2.coefficient = 1_953;
1206     t2.exponent = 0;
1207     assert(t2.toString() == "-1953");
1208 
1209     auto t3 = Decimal!()();
1210     t3.sign = 0;
1211     t3.coefficient = 9_888_555_555;
1212     t3.exponent = -4;
1213     assert(t3.toString() == "988855.5555");
1214 
1215     auto t4 = Decimal!()("300088.44");
1216     assert(t4.toString() == "300088.44");
1217 
1218     auto t5 = Decimal!()("30.5E10");
1219     assert(t5.toString() == "305000000000");
1220 
1221     auto t6 = Decimal!()(10);
1222     assert(t6.toString() == "10");
1223 
1224     auto t7 = Decimal!()(12_345_678);
1225     assert(t7.toString() == "12345678");
1226 
1227     auto t8 = Decimal!()(1234.5678);
1228     assert(t8.toString() == "1234.5678");
1229 
1230     auto t9 = Decimal!()(0.1234);
1231     assert(t9.toString() == "0.1234");
1232 
1233     auto t10 = Decimal!()(1234.0);
1234     assert(t10.toString() == "1234");
1235 
1236     auto t11 = Decimal!()("1.2345678E-7");
1237     assert(t11.toString() == "0.00000012345678");
1238 
1239     auto t12 = Decimal!()("INF");
1240     assert(t12.toString() == "Infinity");
1241 
1242     auto t13 = Decimal!()("-INF");
1243     assert(t13.toString() == "-Infinity");
1244 
1245     auto t14 = Decimal!()("NAN");
1246     assert(t14.toString() == "NaN");
1247 
1248     auto t15 = Decimal!()("-NAN");
1249     assert(t15.toString() == "-NaN");
1250 }
1251 
1252 // test rounding
1253 // @safe pure nothrow
1254 unittest
1255 {
1256     import std.exception : assertThrown;
1257 
1258     static struct Test
1259     {
1260         ulong coefficient;
1261         ulong expected;
1262         bool inexact;
1263         bool rounded;
1264     }
1265 
1266     static struct DownHook
1267     {
1268         enum uint precision = 5;
1269         enum Rounding roundingMode = Rounding.Down;
1270     }
1271 
1272     static struct UpHook
1273     {
1274         enum uint precision = 5;
1275         enum Rounding roundingMode = Rounding.Up;
1276     }
1277 
1278     static struct HalfUpHook
1279     {
1280         enum uint precision = 5;
1281         enum Rounding roundingMode = Rounding.HalfUp;
1282     }
1283 
1284     auto downValues = [
1285         Test(12345, 12345, false, false),
1286         Test(123449, 12344, true, true),
1287         Test(1234499999, 12344, true, true),
1288         Test(123451, 12345, true, true),
1289         Test(123450000001, 12345, true, true),
1290         Test(1234649999, 12346, true, true),
1291         Test(123465, 12346, true, true),
1292         Test(1234650001, 12346, true, true),
1293         Test(1234500, 12345, false, true)
1294     ];
1295     auto upValues = [
1296         Test(12345, 12345, false, false),
1297         Test(1234499, 12345, true, true),
1298         Test(123449999999, 12345, true, true),
1299         Test(123450000001, 12346, true, true),
1300         Test(123451, 12346, true, true),
1301         Test(1234649999, 12347, true, true),
1302         Test(123465, 12347, true, true),
1303         Test(123454, 12346, true, true),
1304         Test(1234500, 12345, false, true)
1305     ];
1306     auto halfUpValues = [
1307         Test(12345, 12345, false, false),
1308         Test(123449, 12345, true, true),
1309         Test(1234499, 12345, true, true),
1310         Test(12344999, 12345, true, true),
1311         Test(123451, 12345, true, true),
1312         Test(1234501, 12345, true, true),
1313         Test(123464999, 12346, true, true),
1314         Test(123465, 12347, true, true),
1315         Test(1234650001, 12347, true, true),
1316         Test(123456, 12346, true, true),
1317         Test(1234500, 12345, false, true)
1318     ];
1319 
1320     foreach (e; downValues)
1321     {
1322         auto d = Decimal!(DownHook)(e.coefficient);
1323         assert(d.coefficient == e.expected);
1324         assert(d.rounded == e.rounded);
1325         assert(d.inexact == e.inexact);
1326     }
1327     foreach (e; upValues)
1328     {
1329         auto d = Decimal!(UpHook)(e.coefficient);
1330         assert(d.coefficient == e.expected);
1331         assert(d.rounded == e.rounded);
1332         assert(d.inexact == e.inexact);
1333     }
1334     foreach (e; halfUpValues)
1335     {
1336         auto d = Decimal!(HalfUpHook)(e.coefficient);
1337         assert(d.coefficient == e.expected);
1338         assert(d.rounded == e.rounded);
1339         assert(d.inexact == e.inexact);
1340     }
1341 
1342     // Test that the exponent is properly changed
1343     auto d1 = decimal!(HalfUpHook)("1.2345678E-7");
1344     assert(d1.exponent == -11);
1345 
1346     // test calling of defined hook methods
1347     static struct ThrowHook
1348     {
1349         enum uint precision = 5;
1350         enum Rounding roundingMode = Rounding.HalfUp;
1351 
1352         static void onRounded(T)(T d)
1353         {
1354             throw new Exception("Rounded");
1355         }
1356     }
1357 
1358     assertThrown!Exception(Decimal!(ThrowHook)(1_234_567));
1359 
1360     // test rounding with BigInt
1361     static struct HigherHook
1362     {
1363         enum uint precision = 16;
1364         enum Rounding roundingMode = Rounding.HalfUp;
1365     }
1366 
1367     auto d2 = decimal!(HigherHook)("10000000000000005");
1368     assert(d2.rounded);
1369     assert(d2.toString() == "10000000000000010");
1370 }
1371 
1372 /**
1373  * Factory function
1374  */
1375 auto decimal(Hook = Abort, R)(R r)
1376 if ((isForwardRange!R &&
1377     isSomeChar!(ElementEncodingType!R) &&
1378     !isInfinite!R) || isNumeric!R)
1379 {
1380     return Decimal!(Hook)(r);
1381 }
1382 
1383 ///
1384 unittest
1385 {
1386     auto d1 = decimal(5.5);
1387     assert(d1.toString == "5.5");
1388 
1389     auto d2 = decimal("500.555");
1390 }
1391 
1392 /**
1393  * Rounding mode
1394  */
1395 enum Rounding
1396 {
1397     /**
1398      * (Round toward 0; truncate.) The discarded digits are ignored; the result is unchanged.
1399      */
1400     Down,
1401     /**
1402      * If the discarded digits represent greater than or equal to half (0.5)
1403      * of the value of a one in the next left position then the result coefficient
1404      * should be incremented by 1 (rounded up). Otherwise the discarded digits are ignored.
1405      */
1406     HalfUp,
1407     /**
1408      * If the discarded digits represent greater than half (0.5) the value of a
1409      * one in the next left position then the result coefficient should be
1410      * incremented by 1 (rounded up). If they represent less than half, then the
1411      * result coefficient is not adjusted (that is, the discarded digits are ignored).
1412      *
1413      * Otherwise (they represent exactly half) the result coefficient is unaltered
1414      * if its rightmost digit is even, or incremented by 1 (rounded up) if its
1415      * rightmost digit is odd (to make an even digit).
1416      */
1417     HalfEven,
1418     /**
1419      * If all of the discarded digits are zero or if the sign is 1 the result is
1420      * unchanged. Otherwise, the result coefficient should be incremented by 1
1421      * (rounded up).
1422      */
1423     Ceiling,
1424     /**
1425      * If all of the discarded digits are zero or if the sign is 0 the result is
1426      * unchanged. Otherwise, the sign is 1 and the result coefficient should be
1427      * incremented by 1.
1428      */
1429     Floor,
1430     /**
1431      * If the discarded digits represent greater than half (0.5) of the value of
1432      * a one in the next left position then the result coefficient should be
1433      * incremented by 1 (rounded up). Otherwise (the discarded digits are 0.5 or
1434      * less) the discarded digits are ignored.
1435      */
1436     HalfDown,
1437     /**
1438      * (Round away from 0.) If all of the discarded digits are zero the result is
1439      * unchanged. Otherwise, the result coefficient should be incremented by 1 (rounded up).
1440      */
1441     Up,
1442     /**
1443      * (Round zero or five away from 0.) The same as round-up, except that rounding
1444      * up only occurs if the digit to be rounded up is 0 or 5, and after overflow
1445      * the result is the same as for round-down.
1446      */
1447     ZeroFiveUp
1448 }
1449 
1450 /**
1451  * Will halt program on division by zero, invalid operations,
1452  * overflows, and underflows
1453  *
1454  * Has 16 significant digits, rounds half up
1455  */
1456 struct Abort
1457 {
1458     ///
1459     enum Rounding roundingMode = Rounding.HalfUp;
1460     /**
1461      * A precision of 9 allows all possible the results of +,-,*, and /
1462      * to fit into a `ulong` with no issues.
1463      */
1464     enum uint precision = 9;
1465 
1466     ///
1467     static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T))
1468     {
1469         assert(0, "Division by zero");
1470     }
1471 
1472     ///
1473     static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T))
1474     {
1475         assert(0, "Invalid operation");
1476     }
1477 
1478     ///
1479     static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T))
1480     {
1481         assert(0, "Overflow");
1482     }
1483 
1484     ///
1485     static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T))
1486     {
1487         assert(0, "Underflow");
1488     }
1489 }
1490 
1491 /**
1492  * Same as abort, but offers 64 significant digits
1493  *
1494  * Note: Using any precision over `9` is an order of magnitude slower
1495  * due to implementation constraints. Only use this if you really need
1496  * data that precise
1497  */
1498 static struct HighPrecision
1499 {
1500     enum Rounding roundingMode = Rounding.HalfUp;
1501     enum uint precision = 64;
1502 
1503     ///
1504     static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T))
1505     {
1506         assert(0, "Division by zero");
1507     }
1508 
1509     ///
1510     static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T))
1511     {
1512         assert(0, "Invalid operation");
1513     }
1514 
1515     ///
1516     static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T))
1517     {
1518         assert(0, "Overflow");
1519     }
1520 
1521     ///
1522     static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T))
1523     {
1524         assert(0, "Underflow");
1525     }
1526 }
1527 
1528 /**
1529  * Will throw exceptions on division by zero, invalid operations,
1530  * overflows, and underflows
1531  *
1532  * Has 16 significant digits, rounds half up
1533  */
1534 struct Throw
1535 {
1536     ///
1537     enum Rounding roundingMode = Rounding.HalfUp;
1538     ///
1539     enum uint precision = 9;
1540 
1541     ///
1542     static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T))
1543     {
1544         throw new DivisionByZero();
1545     }
1546 
1547     ///
1548     static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T))
1549     {
1550         throw new InvalidOperation();
1551     }
1552 
1553     ///
1554     static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T))
1555     {
1556         throw new Overflow();
1557     }
1558 
1559     ///
1560     static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T))
1561     {
1562         throw new Underflow();
1563     }
1564 }
1565 
1566 /**
1567  * Does nothing on invalid operations except the proper flags
1568  *
1569  * Has 16 significant digits, rounds half up
1570  */
1571 struct NoOp
1572 {
1573     ///
1574     enum Rounding roundingMode = Rounding.HalfUp;
1575     ///
1576     enum uint precision = 9;
1577 }
1578 
1579 /**
1580  * Thrown when using $(LREF Throw) and division by zero occurs
1581  */
1582 class DivisionByZero : Exception
1583 {
1584     /++
1585         Params:
1586             msg  = The message for the exception.
1587             file = The file where the exception occurred.
1588             line = The line number where the exception occurred.
1589             next = The previous exception in the chain of exceptions, if any.
1590     +/
1591     this(string msg, string file = __FILE__, size_t line = __LINE__,
1592          Throwable next = null) @nogc @safe pure nothrow
1593     {
1594         super(msg, file, line, next);
1595     }
1596 
1597     /++
1598         Params:
1599             msg  = The message for the exception.
1600             next = The previous exception in the chain of exceptions.
1601             file = The file where the exception occurred.
1602             line = The line number where the exception occurred.
1603     +/
1604     this(string msg, Throwable next, string file = __FILE__,
1605          size_t line = __LINE__) @nogc @safe pure nothrow
1606     {
1607         super(msg, file, line, next);
1608     }
1609 }
1610 
1611 /**
1612  * Thrown when using $(LREF Throw) and an invalid operation occurs
1613  */
1614 class InvalidOperation : Exception
1615 {
1616     /++
1617         Params:
1618             msg  = The message for the exception.
1619             file = The file where the exception occurred.
1620             line = The line number where the exception occurred.
1621             next = The previous exception in the chain of exceptions, if any.
1622     +/
1623     this(string msg, string file = __FILE__, size_t line = __LINE__,
1624          Throwable next = null) @nogc @safe pure nothrow
1625     {
1626         super(msg, file, line, next);
1627     }
1628 
1629     /++
1630         Params:
1631             msg  = The message for the exception.
1632             next = The previous exception in the chain of exceptions.
1633             file = The file where the exception occurred.
1634             line = The line number where the exception occurred.
1635     +/
1636     this(string msg, Throwable next, string file = __FILE__,
1637          size_t line = __LINE__) @nogc @safe pure nothrow
1638     {
1639         super(msg, file, line, next);
1640     }
1641 }
1642 
1643 /**
1644  * Thrown when using $(LREF Throw) and overflow occurs
1645  */
1646 class Overflow : Exception
1647 {
1648     /++
1649         Params:
1650             msg  = The message for the exception.
1651             file = The file where the exception occurred.
1652             line = The line number where the exception occurred.
1653             next = The previous exception in the chain of exceptions, if any.
1654     +/
1655     this(string msg, string file = __FILE__, size_t line = __LINE__,
1656          Throwable next = null) @nogc @safe pure nothrow
1657     {
1658         super(msg, file, line, next);
1659     }
1660 
1661     /++
1662         Params:
1663             msg  = The message for the exception.
1664             next = The previous exception in the chain of exceptions.
1665             file = The file where the exception occurred.
1666             line = The line number where the exception occurred.
1667     +/
1668     this(string msg, Throwable next, string file = __FILE__,
1669          size_t line = __LINE__) @nogc @safe pure nothrow
1670     {
1671         super(msg, file, line, next);
1672     }
1673 }
1674 
1675 /**
1676  * Thrown when using $(LREF Throw) and underflow occurs
1677  */
1678 class Underflow : Exception
1679 {
1680     /++
1681         Params:
1682             msg  = The message for the exception.
1683             file = The file where the exception occurred.
1684             line = The line number where the exception occurred.
1685             next = The previous exception in the chain of exceptions, if any.
1686     +/
1687     this(string msg, string file = __FILE__, size_t line = __LINE__,
1688          Throwable next = null) @nogc @safe pure nothrow
1689     {
1690         super(msg, file, line, next);
1691     }
1692 
1693     /++
1694         Params:
1695             msg  = The message for the exception.
1696             next = The previous exception in the chain of exceptions.
1697             file = The file where the exception occurred.
1698             line = The line number where the exception occurred.
1699     +/
1700     this(string msg, Throwable next, string file = __FILE__,
1701          size_t line = __LINE__) @nogc @safe pure nothrow
1702     {
1703         super(msg, file, line, next);
1704     }
1705 }
1706 
1707 /*
1708  * Get the number of digits in the decimal representation of a number
1709  */
1710 private auto numberOfDigits(T)(T x)
1711 {
1712     import std.algorithm.comparison : max;
1713     import std.math : floor, log10;
1714 
1715     static if (isIntegral!T)
1716     {
1717         static if (is(Signed!T == T))
1718         {
1719             import std.math : abs;
1720             x = abs(x);
1721         }
1722 
1723         return (cast(uint) x.log10.floor.max(0)) + 1;
1724     }
1725     else
1726     {
1727         uint digits;
1728 
1729         if (x < 0)
1730             x *= -1;
1731 
1732         while (x > 0)
1733         {
1734             ++digits;
1735             x /= 10;
1736         }
1737 
1738         return max(digits, 1);
1739     }
1740 }
1741 
1742 @safe @nogc pure nothrow unittest
1743 {
1744     assert(numberOfDigits(0) == 1);
1745     assert(numberOfDigits(1) == 1);
1746     assert(numberOfDigits(1_000UL) == 4);
1747     assert(numberOfDigits(-1_000L) == 4);
1748     assert(numberOfDigits(1_000_000) == 7);
1749     assert(numberOfDigits(-1_000_000) == 7);
1750     assert(numberOfDigits(123_456) == 6);
1751 }
1752 
1753 
1754 @system pure unittest
1755 {
1756     import std.bigint;
1757 
1758     assert(numberOfDigits(BigInt("0")) == 1);
1759     assert(numberOfDigits(BigInt("1")) == 1);
1760     assert(numberOfDigits(BigInt("1_000")) == 4);
1761     assert(numberOfDigits(BigInt("-1_000")) == 4);
1762     assert(numberOfDigits(BigInt("1_000_000")) == 7);
1763     assert(numberOfDigits(BigInt("123_456")) == 6);
1764     assert(numberOfDigits(BigInt("123_456")) == 6);
1765     assert(numberOfDigits(BigInt("123_456_789_101_112_131_415_161")) == 24);
1766 }
1767 
1768 /*
1769  * Detect whether $(D X) is an enum type, or manifest constant.
1770  */
1771 private template isEnum(X...) if (X.length == 1)
1772 {
1773     static if (is(X[0] == enum))
1774     {
1775         enum isEnum = true;
1776     }
1777     else static if (!is(X[0]) &&
1778                     !is(typeof(X[0]) == void) &&
1779                     !isFunction!(X[0]))
1780     {
1781         enum isEnum =
1782             !is(typeof({ auto ptr = &X[0]; }))
1783          && !is(typeof({ enum off = X[0].offsetof; }));
1784     }
1785     else
1786         enum isEnum = false;
1787 }