1 /**
2  * Quick_Start:
3  * ---
4  * import stdxdecimal;
5  *
6  * void main()
7  * {
8  *     auto d1 = decimal("1.23E-10");
9  *     d1 -= decimal("2.00E-10");
10  *     assert(d1.toString() == "-0.000000000077");
11  * }
12  * ---
13  *
14  * This module defines an exact decimal type, `Decimal`, to a specific number of digits.
15  * This is designed to be a drop in replacement for built-in floating point numbers,
16  * allowing all the same possible operations.
17  *
18  * Floating point numbers (`float`, `double`, `real`) are inherently inaccurate because
19  * they $(HTTPS en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems, cannot represent)
20  * all possible numbers with a decimal part. `Decimal` on the other hand, is able to
21  * represent all possible numbers with a decimal part (limited by memory size and `Hook`).
22  *
23  * Adapted from the specification of the
24  * $(HTTP speleotrove.com/decimal/decarith.html, General Decimal Arithmetic).
25  *
26  * Custom_Behavior:
27  *     The behavior of `Decimal` is controlled by the template parameter `Hook`,
28  *     which can be a user defined type or one of the `Hook`s provided by this
29  *     module.
30  *
31  *     The following behaviors are controlled by `Hook`:
32  *
33  *     $(UL
34  *         $(LI The number of significant digits to store.)
35  *         $(LI The rounding method.)
36  *         $(LI The min and max exponents.)
37  *         $(LI What to do when exceptional conditions arise.)
38  *     )
39  *
40  *     The following predefined `Hook`s are available:
41  *
42  *     $(TABLE
43  *         $(TR
44  *             $(TD $(LREF Abort))
45  *             $(TD `precision` is set to 9, rounding is `HalfUp`, and the program
46  *                  will `assert(0)` on divsion by zero, overflow, underflow, and
47  *                  invalid operations.
48  *             )
49  *         )
50  *         $(TR
51  *             $(TD $(LREF Throw))
52  *             $(TD `precision` is set to 9, rounding is `HalfUp`, and the program
53  *                  will throw an exception on divsion by zero, overflow, underflow, and
54  *                  invalid operations.
55  *             )
56  *         )
57  *         $(TR
58  *             $(TD $(LREF HighPrecision))
59  *             $(TD `precision` is set to 64, rounding is `HalfUp`, and the program
60  *                  will `assert(0)` on divsion by zero, overflow, underflow, and
61  *                  invalid operations.
62  *             )
63  *         )
64  *         $(TR
65  *             $(TD $(LREF NoOp))
66  *             $(TD `precision` is set to 9, rounding is `HalfUp`, and nothing will
67  *                  happen on exceptional conditions.
68  *             )
69  *         )
70  *     )
71  * 
72  * Percision_and_Rounding:
73  *     `Decimal` accurately stores as many as `Hook.precision` significant digits.
74  *     Once the number of digits `> Hook.precision`, then the number is rounded.
75  *     Rounding is performed according to the rules laid out in $(LREF RoundingMode).
76  *
77  *     By default, the precision is 9, and the rounding mode is `RoundingMode.HalfUp`.
78  *
79  *     `Hook.precision` must be `<= uint.max - 1` and `> 1`.
80  *
81  * Note_On_Speed:
82  *     The more digits of precision you define in hook, the slower many operations
83  *     will become. It's recommended that you use the least amount of precision
84  *     necessary for your code.
85  *
86  * Exceptional_Conditions:
87  *     Certain operations will cause a `Decimal` to enter into an invalid state,
88  *     e.g. dividing by zero. When this happens, Decimal does two things
89  *
90  *     $(OL
91  *         $(LI Sets a public `bool` variable to `true`.)
92  *         $(LI Calls a specific function in `Hook`, if it exists, with the
93  *         operation's result as the only parameter.)
94  *     )
95  *
96  *    The following table lists all of the conditions
97  *
98  *    $(TABLE
99  *        $(THEAD
100  *            $(TR
101  *                $(TH Name)
102  *                $(TH Flag)
103  *                $(TH Method)
104  *                $(TH Description)
105  *            )
106  *        )
107  *        $(TBODY
108  *            $(TR
109  *                $(TD Clamped)
110  *                $(TD `clamped`)
111  *                $(TD `onClamped`)
112  *                $(TD Occurs when the exponent has been altered to fit in-between
113  *                     `Hook.maxExponent` and `Hook.minExponent`.
114  *                )
115  *            )
116  *            $(TR
117  *                $(TD Inexact)
118  *                $(TD `inexact`)
119  *                $(TD `onInexact`)
120  *                $(TD Occurs when the result of an operation is not perfectly accurate.
121  *                     Mostly occurs when rounding removed non-zero digits.
122  *                )
123  *            )
124  *            $(TR
125  *                $(TD Invalid Operation)
126  *                $(TD `invalidOperation`)
127  *                $(TD `onInvalidOperation`)
128  *                $(TD Flagged when an operation makes no sense, e.g. multiplying `0`
129  *                     and `Infinity` or add -Infinity to Infinity. 
130  *                )
131  *            )
132  *            $(TR
133  *                $(TD Division by Zero)
134  *                $(TD `divisionByZero`)
135  *                $(TD `onDivisionByZero`)
136  *                $(TD Specific invalid operation. Occurs whenever the dividend of a
137  *                     division or modulo is equal to zero.
138  *                )
139  *            )
140  *            $(TR
141  *                $(TD Rounded)
142  *                $(TD `rounded`)
143  *                $(TD `onRounded`)
144  *                $(TD Occurs when the `Decimal`'s result had more than `Hook.precision`
145  *                     significant digits and was reduced.
146  *                )
147  *            )
148  *            $(TR
149  *                $(TD Subnormal)
150  *                $(TD `subnormal`)
151  *                $(TD `onSubnormal`)
152  *                $(TD Flagged when the exponent is less than `Hook.maxExponent` but the
153  *                     digits of the `Decimal` are not inexact.
154  *                )
155  *            )
156  *            $(TR
157  *                $(TD Overflow)
158  *                $(TD `overflow`)
159  *                $(TD `onOverflow`)
160  *                $(TD Not to be confused with integer overflow, this is flagged when
161  *                     the exponent of the result of an operation would have been above
162  *                     `Hook.maxExponent` and the result is inexact. Inexact and Rounded
163  *                     are always set with this flag.
164  *                )
165  *            )
166  *            $(TR
167  *                $(TD Underflow)
168  *                $(TD `underflow`)
169  *                $(TD `onUnderflow`)
170  *                $(TD Not to be confused with integer underflow, this is flagged when
171  *                     the exponent of the result of an operation would have been below
172  *                     `Hook.minExponent`. Inexact, Rounded, and Subnormal are always set with
173  *                     this flag.
174  *                )
175  *            )
176  *        )
177  *    )
178  *
179  *    Each function documentation lists the specific states that will led to one
180  *    of these flags.
181  *
182  * Differences_From_The_Specification:
183  *     $(UL
184  *         $(LI There's no concept of a Signaling NaN in this module.)
185  *         $(LI There's no concept of a Diagnostic NaN in this module.)
186  *         $(LI `compare`, implemented as `opCmp`, does not propagate `NaN` due
187  *         to D's `opCmp` semantics.)
188  *     )
189  *
190  * Version:
191  *     `v0.5`. Still work in progress. For missing features, see `README.md`
192  *
193  * License:
194  *     $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
195  *
196  * Authors:
197  *     Jack Stouffer
198 */
199 module stdxdecimal;
200 
201 version(unittest) { import std.stdio; }
202 import std.range.primitives;
203 import std.traits;
204 import std.bigint;
205 
206 /**
207  * A exact decimal type, accurate to `Hook.precision` digits. Designed to be a
208  * drop in replacement for floating points.
209  * 
210  * Behavior is defined by `Hook`. See the module overview for more information.
211  */
212 struct Decimal(Hook = Abort)
213 {
214     import std.experimental.allocator.common : stateSize;
215 
216     static assert(
217         (hasMember!(Hook, "precision") && is(typeof(Hook.precision) : uint))
218         || !hasMember!(Hook, "precision"),
219         "Hook.precision must be implicitly convertible to uint"
220     );
221     static assert(
222         (hasMember!(Hook, "precision") && isEnum!(Hook.precision))
223         || !hasMember!(Hook, "precision"),
224         "Hook.precision must be readable at compile-time"
225     );
226     static assert(
227         (hasMember!(Hook, "precision") && hook.precision > 1)
228         || !hasMember!(Hook, "precision"),
229         "Hook.precision is too small (must be at least 2)"
230     );
231     static assert(
232         (hasMember!(Hook, "precision") && hook.precision < uint.max)
233         || !hasMember!(Hook, "precision"),
234         "Hook.precision must be < uint.max"
235     );
236     static assert(
237         (hasMember!(Hook, "roundingMode") && is(typeof(Hook.roundingMode) == Rounding))
238         || !hasMember!(Hook, "precision"),
239         "Hook.roundingMode must be of type Rounding"
240     );
241     static assert(
242         (hasMember!(Hook, "precision") && isEnum!(Hook.roundingMode))
243         || !hasMember!(Hook, "precision"),
244         "Hook.roundingMode must be readable at compile-time"
245     );
246     static assert(
247         (hasMember!(Hook, "maxExponent") && isEnum!(Hook.maxExponent))
248         || !hasMember!(Hook, "maxExponent"),
249         "Hook.maxExponent must be readable at compile-time"
250     );
251     static assert(
252         (hasMember!(Hook, "minExponent") && isEnum!(Hook.minExponent))
253         || !hasMember!(Hook, "minExponent"),
254         "Hook.minExponent must be readable at compile-time"
255     );
256 
257     // result of bitfields separated out so the properties can be public
258     private ushort signAndFlags;
259     @property void sign(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 1U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))1U);}
260     @property void isNan(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 2U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))2U);}
261     @property void isInf(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 4U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))4U);}
262     @property void clamped(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 8U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))8U);}
263     @property void divisionByZero(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 16U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))16U);}
264     @property void inexact(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 32U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))32U);}
265     @property void invalidOperation(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 64U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))64U);}
266     @property void overflow(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 128U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))128U);}
267     @property void rounded(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 256U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))256U);}
268     @property void subnormal(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 512U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))512U);}
269     @property void underflow(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 1024U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))1024U);}
270 
271 package:
272     BigInt coefficient;
273     int exponent;
274 
275     // choose between default or defined parameters
276     static if (hasMember!(Hook, "precision"))
277         enum uint precision = Hook.precision;
278     else
279         enum uint precision = 16;
280 
281     static if (hasMember!(Hook, "roundingMode"))
282         enum Rounding roundingMode = Hook.roundingMode;
283     else
284         enum Rounding roundingMode = Rounding.HalfUp;
285 
286     static if (hasMember!(Hook, "maxExponent") && isEnum!(Hook.maxExponent))
287         enum int maxExponent = Hook.maxExponent;
288     else
289         enum int maxExponent = 999;
290 
291     static if (hasMember!(Hook, "minExponent") && isEnum!(Hook.minExponent))
292         enum int minExponent = Hook.minExponent;
293     else
294         enum int minExponent = -999;
295 
296     static assert(
297         minExponent < maxExponent,
298         "minExponent must be less than maxExponent"
299     );
300 
301     enum hasClampedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onClamped(d); });
302     enum hasRoundedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onRounded(d); });
303     enum hasInexactMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInexact(d); });
304     enum hasDivisionByZeroMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onDivisionByZero(d); });
305     enum hasInvalidOperationMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInvalidOperation(d); });
306     enum hasOverflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onOverflow(d); });
307     enum hasSubnormalMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onSubnormal(d); });
308     enum hasUnderflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onUnderflow(d); });
309 
310     /*
311         rounds num via `Hook`s rounding mode.
312 
313         Rounded is always set if the coefficient is changed
314 
315         Inexact is set if the rounded digits were non-zero
316      */
317     auto round(T)(T num) pure
318     {
319         version (D_InlineAsm_X86) {}
320         else
321         {
322             enum BigInt max = BigInt(10) ^^ precision;
323             if (num < max)
324                 return num;
325         }
326 
327         auto digits = numberOfDigits(num);
328         if (digits <= precision)
329             return num;
330 
331         Unqual!(T) lastDigit;
332 
333         // TODO: as soon as inexact == true, we can quit the
334         // loops and do a single division
335         static if (roundingMode == Rounding.Down)
336         {
337             while (digits > precision)
338             {
339                 if (!inexact)
340                     lastDigit = num % 10;
341 
342                 num /= 10;
343 
344                 --digits;
345                 ++exponent;
346 
347                 if (!inexact && lastDigit != 0)
348                     inexact = true;
349             }
350         }
351         else static if (roundingMode == Rounding.Up)
352         {
353             while (digits > precision)
354             {
355                 if (!inexact)
356                     lastDigit = num % 10;
357 
358                 num /= 10;
359 
360                 --digits;
361                 ++exponent;
362 
363                 if (!inexact && lastDigit != 0)
364                     inexact = true;
365             }
366 
367             // If all of the discarded digits are zero the result is unchanged
368             if (inexact)
369                 ++num;
370         }
371         else static if (roundingMode == Rounding.HalfUp)
372         {
373             while (digits > precision + 1)
374             {
375                 if (!inexact)
376                     lastDigit = num % 10;
377 
378                 num /= 10;
379 
380                 --digits;
381                 ++exponent;
382 
383                 if (!inexact && lastDigit != 0)
384                     inexact = true;
385             }
386 
387             lastDigit = num % 10;
388             num /= 10;
389 
390             if (lastDigit != 0)
391                 inexact = true;
392             ++exponent;
393 
394             if (lastDigit >= 5)
395                 ++num;
396         }
397         else
398         {
399             static assert(0, "Not implemented");
400         }
401 
402         rounded = true;
403 
404         static if (hasInexactMethod)
405             if (inexact)
406                 hook.onInexact(this);
407 
408         static if (hasRoundedMethod)
409             hook.onRounded(this);
410 
411         return num;
412     }
413 
414     /*
415         Separated into its own function for readability as well as
416         allowing opCmp to skip the rounding step
417      */
418     ref Decimal!(Hook) addImpl(string op, bool doRound, T)(T rhs)
419     {
420         import std.algorithm.comparison : min;
421         import std.math : abs;
422         import std.conv : to;
423 
424         bool rhsSign;
425 
426         static if (op == "-")
427             rhsSign = rhs.sign == 0 ? 1 : 0;
428         else
429             rhsSign = rhs.sign;
430 
431         if (isNan || rhs.isNan)
432         {
433             isNan = true;
434             isInf = false;
435 
436             // the sign of the first isNan is simply propagated
437             if (isNan)
438             {
439                 sign = sign;
440             }
441             else if (rhs.isNan)
442             {
443                 static if (op == "-")
444                     sign = 0 ? 1 : 0;
445                 else
446                     sign = sign;
447             }
448 
449             coefficient = 0;
450             exponent = 0;
451             return this;
452         }
453 
454         if (isInf && rhs.isInf)
455         {
456             if (sign == 1 && rhsSign == 1)
457             {
458                 sign = 1;
459                 isInf = true;
460                 return this;
461             }
462 
463             if (sign == 0 && rhsSign == 0)
464             {
465                 isInf = true;
466                 return this;
467             }
468 
469             // -Inf + Inf makes no sense
470             isNan = true;
471             isInf = false;
472             coefficient = 0;
473             exponent = 0;
474             sign = 0;
475             invalidOperation = true;
476             static if (hasInvalidOperationMethod)
477                 hook.onInvalidOperation(this);
478             return this;
479         }
480 
481         if (isInf)
482         {
483             isInf = true;
484             sign = sign;
485             coefficient = 0;
486             exponent = 0;
487             return this;
488         }
489 
490         if (rhs.isInf)
491         {
492             isInf = true;
493             sign = rhsSign;
494             coefficient = 0;
495             exponent = 0;
496             return this;
497         }
498 
499         BigInt alignedCoefficient = coefficient;
500         BigInt rhsAlignedCoefficient = rhs.coefficient;
501 
502         if (exponent != rhs.exponent)
503         {
504             long diff;
505 
506             if (exponent > rhs.exponent)
507             {
508                 diff = abs(exponent - rhs.exponent);
509                 alignedCoefficient *= BigInt(10) ^^ diff;
510             }
511             else
512             {
513                 diff = abs(rhs.exponent - exponent);
514                 rhsAlignedCoefficient *= BigInt(10) ^^ diff;
515             }
516         }
517 
518         exponent = min(exponent, rhs.exponent);
519         // If the signs of the operands differ then the smaller aligned coefficient
520         // is subtracted from the larger; otherwise they are added.
521         if (sign == rhsSign)
522         {
523             coefficient = alignedCoefficient + rhsAlignedCoefficient;
524         }
525         else
526         {
527             if (alignedCoefficient >= rhsAlignedCoefficient)
528             {
529                 coefficient = alignedCoefficient - rhsAlignedCoefficient;
530             }
531             else
532             {
533                 coefficient = rhsAlignedCoefficient - alignedCoefficient;
534             }
535         }
536 
537         if (coefficient != 0)
538         {
539             // the sign of the result is the sign of the operand having
540             // the larger absolute value.
541             if (alignedCoefficient >= rhsAlignedCoefficient)
542                 sign = sign;
543             else
544                 sign = rhsSign;
545         }
546         else
547         {
548             if (sign == 1 && rhsSign == 1)
549                 sign = 1;
550             else
551                 sign = 0;
552 
553             static if (roundingMode == Rounding.Floor)
554                 if (sign != rhsSign)
555                     sign = 1;
556         }
557 
558         static if (doRound)
559             coefficient = round(coefficient);
560 
561         return this;
562     }
563 
564 public:
565     /**
566      * `hook` is a member variable if it has state, or an alias for `Hook`
567      * otherwise.
568      */
569     static if (stateSize!Hook > 0)
570         Hook hook;
571     else
572         alias hook = Hook;
573 
574     /**
575      * Constructs an exact decimal type from a built in number
576      * 
577      * Params:
578      *     num = the number to convert to exact decimal
579      * 
580      * Note:
581      *     Using `float` types for construction is less accurate than using a string
582      *     representation due to floating point inaccuracy. If possible, it's always
583      *     better to use string construction.
584      */
585     this(T)(T num) pure // for some reason doesn't infer pure
586     if (isNumeric!T)
587     {
588         opAssign(num);
589     }
590 
591     /**
592      * Converts a string representing a number to an exact decimal.
593      *
594      * If the string does not represent a number, then the result is `NaN`
595      * and `invalidOperation` is `true`.
596      *
597      * Params:
598      *     str = The string to convert from
599      *
600      * Specification:
601      * -------
602      * sign           ::=  + | -
603      * digit          ::=  0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
604      * indicator      ::=  e | E
605      * digits         ::=  digit [digit]...
606      * decimal-part   ::=  digits . [digits] | [.] digits
607      * exponent-part  ::=  indicator [sign] digits
608      * infinity       ::=  Infinity | Inf
609      * nan            ::=  NaN
610      * numeric-value  ::=  decimal-part [exponent-part] | infinity
611      * numeric-string ::=  [sign] numeric-value | [sign] nan
612      * -------
613      *
614      * Exceptional_Conditions:
615      *     `invalidOperation` is flagged when `str` is not a valid string
616      */
617     this(S)(S str)
618     if (isForwardRange!S && isSomeChar!(ElementEncodingType!S) && !isInfinite!S && !isSomeString!S)
619     {
620         import std.algorithm.comparison : among, equal;
621         import std.algorithm.iteration : filter, map;
622         import std.algorithm.searching : startsWith;
623         import std.ascii : isDigit, toLower;
624         import std.utf : byChar, byCodeUnit;
625 
626         static bool asciiCmp(S1)(S1 a, string b)
627         {
628             return a.map!toLower.equal(b.byChar.map!toLower);
629         }
630 
631         // TODO: filter out underscores
632 
633         if (str.empty)
634         {
635             isNan = true;
636             return;
637         }
638 
639         immutable frontResult = str.front;
640         bool sawDecimal = false;
641         bool sawExponent = false;
642         bool sawExponentSign = false;
643         byte exponentSign;
644         int sciExponent = 0;
645 
646         if (frontResult == '+')
647         {
648             str.popFront;
649         }
650         else if (frontResult == '-')
651         {
652             sign = 1;
653             str.popFront;
654         }
655 
656         if (str.empty)
657         {
658             sign = 0;
659             goto Lerr;
660         }
661 
662         if (str.among!((a, b) => asciiCmp(a.save, b))
663                ("inf", "infinity"))
664         {
665             isInf = true;
666             return;
667         }
668 
669         // having numbers after nan is valid in the spec
670         if (str.save.map!toLower.startsWith("nan".byChar))
671         {
672             isNan = true;
673             return;
674         }
675 
676         // leading zeros
677         while (!str.empty && str.front == '0')
678             str.popFront;
679 
680         for (; !str.empty; str.popFront)
681         {
682             auto digit = str.front;
683 
684             if (isDigit(digit))
685             {
686                 if (!sawExponent)
687                 {
688                     coefficient *= 10;
689                     coefficient += cast(uint) (digit - '0');
690                 }
691 
692                 if (sawDecimal && !sawExponent)
693                     exponent--;
694 
695                 if (sawExponent)
696                 {
697                     while (!str.empty)
698                     {
699                         if (!isDigit(str.front))
700                             goto Lerr;
701 
702                         sciExponent += cast(uint) (str.front - '0');
703                         if (!str.empty)
704                         {
705                             str.popFront;
706                             if (!str.empty)
707                                 sciExponent *= 10;
708                         }
709                     }
710 
711                     if (sawExponentSign && exponentSign == -1)
712                         sciExponent *= -1;
713 
714                     exponent += sciExponent;
715 
716                     if (str.empty)
717                     {
718                         coefficient = round(coefficient);
719                         return;
720                     }
721                 }
722 
723                 continue;
724             }
725 
726             if (digit == '+' || digit == '-')
727             {
728                 // already have exponent sign, bad input so cancel out
729                 if (sawExponentSign)
730                     goto Lerr;
731 
732                 if (sawExponent)
733                 {
734                     if (digit == '-')
735                         exponentSign = -1;
736                     sawExponentSign = true;
737                 }
738                 else
739                 { // no exponent yet, bad input so cancel out
740                     goto Lerr;
741                 }
742 
743                 continue;
744             }
745 
746             if (digit == '.')
747             {
748                 // already have decimal, bad input so cancel out
749                 if (sawDecimal)
750                     goto Lerr;
751 
752                 sawDecimal = true;
753                 continue;
754             }
755 
756             if (digit.toLower == 'e')
757             {
758                 // already have exponent, bad input so cancel out
759                 if (sawExponent)
760                     goto Lerr;
761 
762                 sawExponent = true;
763                 continue;
764             }
765 
766             goto Lerr;
767         }
768 
769         coefficient = round(coefficient);
770         return;
771 
772         Lerr:
773             isNan = true;
774             coefficient = 0;
775             exponent = 0;
776 
777             invalidOperation = true;
778             static if (hasInvalidOperationMethod)
779                 hook.onInvalidOperation(this);
780 
781             return;
782     }
783 
784     /// ditto
785     this(S)(S str) pure if (isSomeString!S)
786     {
787         // hack to allow pure annotation for immutable construction
788         // see Issue 17330
789         import std.utf : byCodeUnit;
790         this(str.byCodeUnit);
791     }
792 
793     /**
794      * Changes the value of this decimal to the value of a built-in number.
795      * Also resets all exceptional condition flags.
796      *
797      * Params:
798      *     num = the number to convert to exact decimal
799      * 
800      * Note:
801      *     Using `float` types for construction is less accurate than using a string
802      *     representation due to floating point inaccuracy. If possible, it's always
803      *     better to use string construction.
804      */
805     auto opAssign(T)(T num) if (isNumeric!T)
806     {
807         resetFlags();
808 
809         // the behavior of conversion from built-in number types
810         // isn't covered by the spec, so we can do whatever we
811         // want here
812         static if (isIntegral!T)
813         {
814             static if (isSigned!T)
815             {
816                 import std.math : abs;
817 
818                 // work around int.min bug where abs(int.min) == int.min
819                 static if (T.sizeof <= int.sizeof)
820                     coefficient = abs(cast(long) num);
821                 else
822                     coefficient = abs(num);
823                 
824                 sign = num >= 0 ? 0 : 1;
825             }
826             else
827             {
828                 coefficient = num;
829             }
830         }
831         else
832         {
833             import std.math : abs, isInfinity, isIdentical, isNaN;
834 
835             Unqual!T val = num;
836 
837             if (isInfinity(val))
838             {
839                 isInf = true;
840                 sign = val > 0 ? 0 : 1;
841                 return this;
842             }
843 
844             if (isNaN(val))
845             {
846                 isNan = true;
847                 sign = isIdentical(val, T.nan) ? 0 : 1;
848                 return this;
849             }
850 
851             sign = val >= 0 ? 0 : 1;
852             val = abs(val);
853 
854             // while the number still has a fractional part, multiply by 10,
855             // counting each time until no fractional part
856             Unqual!T fraction = val - (cast(long) val);
857             while (fraction > 0)
858             {
859                 --exponent;
860                 val *= 10;
861                 fraction = val - (cast(long) val);
862             }
863 
864             coefficient = cast(size_t) val;
865         }
866 
867         coefficient = round(coefficient);
868         return this;
869     }
870 
871     /**
872      * Performs a binary operation between two decimals, or a decimal and
873      * a built in number.
874      * 
875      * The result has the hook of the left hand side. On non-assignment
876      * operations, invalid operations do not effect the left hand side of
877      * the operation, only the result.
878      *
879      * When the right hand side is a built-in numeric type, the default
880      * hook `Abort` is used for its decimal representation in the operation.
881      *
882      * Params:
883      *     rhs = the right-hand side of the operation
884      *
885      * Exceptional_Conditions:
886      *     `invalidOperation` is flagged under the following conditions
887      *     $(UL
888      *         $(LI Adding Infinity and -Infinity, and vice-versa)
889      *         $(LI Multiplying +/-Infinity by +/-zero)
890      *         $(LI Dividing anything but zero by zero)
891      *         $(LI Dividing +/-Infinity by +/-Infinity)
892      *     )
893      *     `divisionByZero` is flagged when dividing anything but zero by zero
894      */
895     auto opBinary(string op, T)(T rhs) const
896     if (op == "+" || op == "-" || op == "*" || op == "/")
897     {
898         auto lhs = dup();
899         return lhs.opOpAssign!(op, T)(rhs);
900     }
901 
902     /// ditto
903     ref Decimal!(Hook) opOpAssign(string op, T)(T rhs)
904     if (op == "+" || op == "-" || op == "*" || op == "/")
905     {
906         static if (isNumeric!T)
907         {
908             auto temp = decimal(rhs);
909             return mixin("this " ~ op ~ "= temp");
910         }
911         else static if (op == "+" || op == "-")
912         {
913             return addImpl!(op, true)(rhs);
914         }
915         else static if (op == "*")
916         {
917             if (isNan || rhs.isNan)
918             {
919                 // the sign of the first nan is simply propagated
920                 if (isNan)
921                     sign = sign;
922                 else if (rhs.isNan)
923                     sign = rhs.sign;
924 
925                 isNan = true;
926                 isInf = false;
927                 coefficient = 0;
928                 exponent = 0;
929 
930                 return this;
931             }
932 
933             sign = sign ^ rhs.sign;
934 
935             if (isInf && rhs.isInf)
936             {
937                 isInf = true;
938                 coefficient = 0;
939                 exponent = 0;
940                 return this;
941             }
942 
943             if (isInf || rhs.isInf)
944             {
945                 if ((isInf && rhs.coefficient == 0) || (rhs.isInf && coefficient == 0))
946                 {
947                     isNan = true;
948                     coefficient = 0;
949                     exponent = 0;
950                     invalidOperation = true;
951                     static if (hasInvalidOperationMethod)
952                         hook.onInvalidOperation(this);
953                 }
954                 else
955                 {
956                     isInf = true;
957                     coefficient = 0;
958                     exponent = 0;
959                 }
960 
961                 return this;
962             }
963 
964             static if (is(typeof(coefficient) == typeof(rhs.coefficient)))
965             {
966                 coefficient = coefficient * rhs.coefficient;
967             }
968             else
969             {
970                 import std.conv : to;
971                 coefficient = coefficient * to!(typeof(coefficient))(rhs.coefficient);
972             }
973             
974             exponent = exponent + rhs.exponent;
975 
976             coefficient = round(coefficient);
977             return this;
978         }
979         else static if (op == "/")
980         {
981             if (isNan || rhs.isNan)
982             {
983                 // the sign of the first nan is simply propagated
984                 if (isNan)
985                     sign = sign;
986                 else if (rhs.isNan)
987                     sign = rhs.sign;
988 
989                 isInf = false;
990                 isNan = true;
991                 coefficient = 0;
992                 exponent = 0;
993 
994                 return this;
995             }
996 
997             if (isInf && rhs.isInf)
998             {
999                 isInf = false;
1000                 isNan = true;
1001                 coefficient = 0;
1002                 exponent = 0;
1003                 sign = 0;
1004                 invalidOperation = true;
1005                 static if (hasInvalidOperationMethod)
1006                     hook.onInvalidOperation(this);
1007                 return this;
1008             }
1009 
1010             if (rhs.coefficient == 0 && coefficient == 0)
1011             {
1012                 isNan = true;
1013                 coefficient = 0;
1014                 exponent = 0;
1015                 divisionByZero = true;
1016 
1017                 static if (hasDivisionByZeroMethod)
1018                     hook.onDivisionByZero(this);
1019 
1020                 return this;
1021             }
1022 
1023             sign = sign ^ rhs.sign;
1024 
1025             if (isInf && !rhs.isInf)
1026             {
1027                 isInf = true;
1028                 coefficient = 0;
1029                 exponent = 0;
1030                 return this;
1031             }
1032 
1033             if (!isInf && rhs.isInf)
1034             {
1035                 coefficient = 0;
1036                 exponent = 0;
1037                 return this;
1038             }
1039 
1040             if (rhs.coefficient == 0 && coefficient != 0)
1041             {
1042                 divisionByZero = true;
1043                 invalidOperation = true;
1044                 isInf = true;
1045 
1046                 static if (hasDivisionByZeroMethod)
1047                     hook.onDivisionByZero(this);
1048                 static if (hasInvalidOperationMethod)
1049                     hook.onInvalidOperation(this);
1050 
1051                 return this;
1052             }
1053 
1054             int adjust;
1055             BigInt res;
1056             BigInt dividend = coefficient;
1057             BigInt divisor = rhs.coefficient;
1058 
1059             if (dividend !=0)
1060             {
1061                 while (dividend < divisor)
1062                 {
1063                     dividend *= 10;
1064                     ++adjust;
1065                 }
1066 
1067                 while (dividend >= divisor * 10)
1068                 {
1069                     divisor *= 10;
1070                     --adjust;
1071                 }
1072 
1073                 while (true)
1074                 {
1075                     while (divisor <= dividend)
1076                     {
1077                         dividend -= divisor;
1078                         ++res;
1079                     }
1080 
1081                     if ((dividend == 0 && adjust >= 0) || numberOfDigits(res) == precision + 1)
1082                     {
1083                         break;
1084                     }
1085                     else
1086                     {
1087                         res *= 10;
1088                         dividend *= 10;
1089                         ++adjust;
1090                     }
1091                 }
1092             }
1093 
1094             coefficient = round(res);
1095             exponent = exponent - (rhs.exponent + adjust);
1096             return this;
1097         }
1098         else
1099         {
1100             static assert(0, "Not implemented yet");
1101         }
1102     }
1103 
1104     /**
1105      * Returns:
1106      *     `+` simply returns a copy of `this` unchanged. `-` returns a
1107      *     copy of `this` with the sign flipped for everything but `0`
1108      *     and `NaN`s.
1109      *
1110      *     Does not modify the decimal in place.
1111      */
1112     auto opUnary(string op)() const
1113     if (op == "-" || op == "+")
1114     {
1115         auto res = dup();
1116 
1117         static if (op == "-")
1118             if ((!isNan && coefficient != 0) || isInf)
1119                 res.sign = sign == 0 ? 1 : 0;
1120 
1121         return res;
1122     }
1123 
1124     /**
1125      * Modifies the decimal in place by adding or subtracting 1 for
1126      * `++` and `--` respectively.
1127      */
1128     ref Decimal!(Hook) opUnary(string op)()
1129     if (op == "++" || op == "--")
1130     {
1131         static immutable one = decimal(1);
1132         static if (op == "++")
1133             this += one;
1134         else
1135             this -= one;
1136 
1137         return this;
1138     }
1139 
1140     // goes against D's normal nan rules because they're really annoying when
1141     // sorting an array of floating point numbers
1142     /**
1143      * `-Infinity` is less than all numbers, `-NaN` is greater than `-Infinity` but
1144      * less than all other numbers, `NaN` is greater than `-NaN` but less than all other
1145      * numbers and `Infinity` is greater than all numbers. `-NaN` and `NaN` are equal to
1146      * themselves.
1147      *
1148      * Params:
1149      *     d = the decimal or built-in number to compare to
1150      *
1151      * Returns:
1152      *     Barring special values, `0` if subtracting the two numbers yields
1153      *     `0`, `-1` if the result is less than `0`, and `1` if the result is
1154      *     greater than zero
1155      */
1156     int opCmp(T)(T d) const if (isNumeric!T || isInstanceOf!(TemplateOf!(Decimal), T))
1157     {
1158         static if (!isNumeric!T)
1159         {
1160             if (isInf)
1161             {
1162                 if (sign == 1 && (isInf != d.isInf || sign != d.sign))
1163                     return -1;
1164                 if (sign == 0 && (isInf != d.isInf || sign != d.sign))
1165                     return 1;
1166                 if (d.isInf && sign == d.sign)
1167                     return 0;
1168             }
1169 
1170             if (isNan && d.isNan)
1171             {
1172                 if (sign == d.sign)
1173                     return 0;
1174                 if (sign == 1)
1175                     return -1;
1176 
1177                 return 1;
1178             }
1179 
1180             if (isNan && !d.isNan)
1181                 return -1;
1182             if (!isNan && d.isNan)
1183                 return 1;
1184             if (sign != d.sign && coefficient != 0 && d.coefficient != 0)
1185                 return sign ? -1 : 1;
1186             if (coefficient == d.coefficient && coefficient == 0)
1187                 return 0;
1188 
1189             auto lhs = dup();
1190             lhs.addImpl!("-", false)(d);
1191 
1192             if (lhs.sign == 0)
1193             {
1194                 if (lhs.coefficient == 0)
1195                     return 0;
1196                 else
1197                     return 1;
1198             }
1199 
1200             return -1;
1201         }
1202         else
1203         {
1204             static if (isIntegral!T)
1205             {
1206                 if (exponent == 0 && sign == 0)
1207                 {
1208                     if (coefficient == d)
1209                         return 0;
1210                     if (coefficient < d)
1211                         return -1;
1212                     if (coefficient > d)
1213                         return 1;
1214                 }
1215             }
1216 
1217             return this.opCmp(d.decimal);
1218         }
1219     }
1220 
1221     /// Returns: `true` if `opCmp` would return `0`
1222     bool opEquals(T)(T d) const
1223     {
1224         return this.opCmp(d) == 0;
1225     }
1226 
1227     /**
1228      * Throws:
1229      *     A `ConvException` if `isIntegral!T` and the decimal is NaN or Infinite.
1230      *
1231      *     A `ConvOverflowException` if `isIntegral!T` and the decimal's value
1232      *     is outside of `T.min` and `T.max`.
1233      * Returns:
1234      *     For `bool`, follows the normal `cast(bool)` rules for floats in D.
1235      *     Numbers `<= -1` returns `true`, numbers between `-1` and `1` return
1236      *     false, numbers `>= 1` return `true`.
1237      *
1238      *     For floating point types, returns a floating point type as close to the
1239      *     decimal as possible.
1240      */
1241     auto opCast(T)() const
1242     if (is(T == bool) || isNumeric!T)
1243     {
1244         static if (is(T == bool))
1245         {
1246             static immutable negone = decimal(-1);
1247             static immutable one = decimal(1);
1248 
1249             if (isNan || isInf)
1250                 return true;
1251 
1252             if (this <= negone || this >= one)
1253                 return true;
1254 
1255             return false;
1256         }
1257         else static if (isIntegral!T)
1258         {
1259             import std.algorithm.comparison : max;
1260             import std.conv : to, text, ConvException, ConvOverflowException;
1261             import std.math : abs;
1262 
1263             if (isNan || isInf)
1264                 throw new ConvException(text(
1265                     "Can't cast ", toDecimalString(), " ", Decimal.stringof, " to ", T.stringof
1266                 ));
1267             if (this > T.max || this < T.min)
1268                 throw new ConvOverflowException(text(
1269                     "Can't cast ", toDecimalString(), " ", Decimal.stringof, " to ", T.stringof
1270                 ));
1271 
1272             T res = to!(T)(coefficient);
1273 
1274             if (exponent < 0)
1275                 res /= max(10 ^^ abs(exponent), 1);
1276             if (exponent > 0)
1277                 res *= max(10 ^^ abs(exponent), 1);
1278 
1279             if (sign)
1280                 res *= -1;
1281 
1282             return res;
1283         }
1284         else
1285         {
1286             import std.conv : to;
1287 
1288             if (isInf)
1289             {
1290                 if (sign == 0)
1291                     return T.infinity;
1292                 return -T.infinity;
1293             }
1294             if (isNan)
1295             {
1296                 if (sign == 0)
1297                     return T.nan;
1298                 return -T.nan;
1299             }
1300 
1301             // this really needs to be reworked, the problem really
1302             // is that both BigInt and uint128 both cast to ints but
1303             // not to floats, and casting to ints cuts off more than
1304             // half of the number, this method however gets pretty close
1305             // to the equivalent floating point representation of the
1306             // decimal
1307             T res = coefficient.toDecimalString.to!T;
1308             
1309             res *= 10.0 ^^ exponent;
1310             if (sign == 1)
1311                 res *= -1;
1312             return res;
1313         }
1314     }
1315 
1316     /**
1317      * Convenience function to reset all exceptional condition flags to `false` at once
1318      */
1319     void resetFlags() @safe @nogc pure nothrow
1320     {
1321         clamped = false;
1322         divisionByZero = false;
1323         inexact = false;
1324         invalidOperation = false;
1325         overflow = false;
1326         rounded = false;
1327         subnormal = false;
1328         underflow = false;
1329     }
1330 
1331     /// Returns: A mutable copy of this `Decimal`. Also copies current flags.
1332     Decimal!(Hook) dup()() const
1333     {
1334         Unqual!(typeof(this)) res;
1335         res.coefficient = coefficient;
1336         res.exponent = exponent;
1337         res.sign = sign;
1338         res.isNan = isNan;
1339         res.isInf = isInf;
1340         res.clamped = clamped;
1341         res.divisionByZero = divisionByZero;
1342         res.inexact = inexact;
1343         res.invalidOperation = invalidOperation;
1344         res.overflow = overflow;
1345         res.rounded = rounded;
1346         res.subnormal = subnormal;
1347         res.underflow = underflow;
1348 
1349         return res;
1350     }
1351 
1352     /// Returns: An immutable copy of this `Decimal`. Also copies current flags.
1353     immutable(Decimal!(Hook)) idup()() const
1354     {
1355         return dup!()();
1356     }
1357 
1358     /// Returns: A decimal representing a positive NaN
1359     static Decimal!(Hook) nan()() @property
1360     {
1361         Decimal!(Hook) res;
1362         res.isNan = true;
1363         return res;
1364     }
1365 
1366     /// Returns: A decimal representing positive Infinity
1367     static Decimal!(Hook) infinity()() @property
1368     {
1369         Decimal!(Hook) res;
1370         res.isInf = true;
1371         return res;
1372     }
1373 
1374     /**
1375      * Returns: The maximum value that this decimal type can represent.
1376      * Equal to `(1 * 10 ^^ (maxExponent + 1)) - 1`
1377      */
1378     static Decimal!(Hook) max()() @property
1379     {
1380         import std.range : repeat;
1381         Decimal!(Hook) res;
1382         res.coefficient = BigInt('9'.repeat(precision));
1383         res.exponent = maxExponent;
1384         return res;
1385     }
1386 
1387     /**
1388      * Returns: The minimum value that this decimal type can represent.
1389      * Equal to `-1 * 10 ^^ minExponent`
1390      */
1391     static Decimal!(Hook) min()() @property
1392     {
1393         Decimal!(Hook) res;
1394         res.coefficient = 1;
1395         res.exponent = minExponent;
1396         res.sign = 1;
1397         return res;
1398     }
1399 
1400     /// Exceptional condition flags
1401     bool sign() @safe pure nothrow @nogc const @property { return (signAndFlags & 1U) != 0; }
1402     /// ditto
1403     bool isNan() @safe pure nothrow @nogc const @property { return (signAndFlags & 2U) != 0;}
1404     /// ditto
1405     bool isInf() @safe pure nothrow @nogc const @property { return (signAndFlags & 4U) != 0;}
1406     /// ditto
1407     bool clamped() @safe pure nothrow @nogc const @property { return (signAndFlags & 8U) != 0;}
1408     /// ditto
1409     bool divisionByZero() @safe pure nothrow @nogc const @property { return (signAndFlags & 16U) != 0;}
1410     /// ditto
1411     bool inexact() @safe pure nothrow @nogc const @property { return (signAndFlags & 32U) != 0;}
1412     /// ditto
1413     bool invalidOperation() @safe pure nothrow @nogc const @property { return (signAndFlags & 64U) != 0;}
1414     /// ditto
1415     bool overflow() @safe pure nothrow @nogc const @property { return (signAndFlags & 128U) != 0;}
1416     /// ditto
1417     bool rounded() @safe pure nothrow @nogc const @property { return (signAndFlags & 256U) != 0;}
1418     /// ditto
1419     bool subnormal() @safe pure nothrow @nogc const @property { return (signAndFlags & 512U) != 0;}
1420     /// ditto
1421     bool underflow() @safe pure nothrow @nogc const @property { return (signAndFlags & 1024U) != 0;}
1422 
1423     ///
1424     alias toString = toDecimalString;
1425 
1426     /// Returns: Returns the decimal string representation of this decimal.
1427     auto toDecimalString() const
1428     {
1429         import std.array : appender;
1430         import std.math : abs;
1431 
1432         auto app = appender!string();
1433         if (exponent > 10 || exponent < -10)
1434             app.reserve(abs(exponent) + precision);
1435         toDecimalString(app);
1436         return app.data;
1437     }
1438 
1439     /// ditto
1440     void toDecimalString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char))
1441     {
1442         import std.math : pow;
1443         import std.range : repeat;
1444 
1445         if (sign == 1)
1446             put(w, '-');
1447 
1448         if (isInf)
1449         {
1450             put(w, "Infinity");
1451             return;
1452         }
1453 
1454         if (isNan)
1455         {
1456             put(w, "NaN");
1457             return;
1458         }
1459 
1460         auto temp = coefficient.toDecimalString;
1461         auto decimalPlace = exponent * -1;
1462 
1463         if (decimalPlace > 0)
1464         {
1465             if (temp.length - decimalPlace == 0)
1466             {
1467                 put(w, "0.");
1468                 put(w, temp);
1469                 return;
1470             }
1471 
1472             if ((cast(long) temp.length) - decimalPlace > 0)
1473             {
1474                 put(w, temp[0 .. $ - decimalPlace]);
1475                 put(w, '.');
1476                 put(w, temp[$ - decimalPlace .. $]);
1477                 return;
1478             }
1479 
1480             if ((cast(long) temp.length) - decimalPlace < 0)
1481             {
1482                 put(w, "0.");
1483                 put(w, '0'.repeat(decimalPlace - temp.length));
1484                 put(w, temp);
1485                 return;
1486             }
1487         }
1488 
1489         if (decimalPlace < 0)
1490         {
1491             put(w, temp);
1492             put(w, '0'.repeat(exponent));
1493             return;
1494         }
1495 
1496         put(w, temp);
1497     }
1498 }
1499 
1500 // string construction
1501 @system
1502 unittest
1503 {
1504     static struct Test
1505     {
1506         string val;
1507         ubyte sign;
1508         int coefficient;
1509         long exponent;
1510     }
1511 
1512     static struct SpecialTest
1513     {
1514         string val;
1515         ubyte sign;
1516         bool isNan;
1517         bool isInf;
1518         bool invalid;
1519     }
1520 
1521     auto nonspecialTestValues = [
1522         Test("0", 0, 0, 0),
1523         Test("+0", 0, 0, 0),
1524         Test("-0", 1, 0, 0),
1525         Test("1.0", 0, 10, -1),
1526         Test("0E+7", 0, 0, 7),
1527         Test("-0E-7", 1, 0, -7),
1528         Test("1.23E3", 0, 123, 1),
1529         Test("0001.0000", 0, 10_000, -4),
1530         Test("-10.0004", 1, 100_004, -4),
1531         Test("+15", 0, 15, 0),
1532         Test("-15", 1, 15, 0),
1533         Test("1234.5E-4", 0, 12_345, -5),
1534         Test("30.5E10", 0, 305, 9) 
1535     ];
1536 
1537     auto specialTestValues = [
1538         SpecialTest("NaN", 0, true, false, false),
1539         SpecialTest("+nan", 0, true, false, false),
1540         SpecialTest("-nan", 1, true, false, false),
1541         SpecialTest("-NAN", 1, true, false, false),
1542         SpecialTest("Infinite", 0, true, false, true),
1543         SpecialTest("infinity", 0, false, true, false),
1544         SpecialTest("-INFINITY", 1, false, true, false),
1545         SpecialTest("inf", 0, false, true, false),
1546         SpecialTest("-inf", 1, false, true, false),
1547         SpecialTest("Jack", 0, true, false, true),
1548         SpecialTest("+", 0, true, false, true),
1549         SpecialTest("-", 0, true, false, true),
1550         SpecialTest("nan0123", 0, true, false, false),
1551         SpecialTest("-nan0123", 1, true, false, false),
1552         SpecialTest("12+3", 0, true, false, true),
1553         SpecialTest("1.2.3", 0, true, false, true),
1554         SpecialTest("123.0E+7E+7", 0, true, false, true),
1555     ];
1556 
1557     foreach (el; nonspecialTestValues)
1558     {
1559         auto d = Decimal!()(el.val);
1560         assert(d.coefficient == el.coefficient);
1561         assert(d.sign == el.sign);
1562         assert(d.exponent == el.exponent);
1563     }
1564 
1565     foreach (el; specialTestValues)
1566     {
1567         auto d = Decimal!(NoOp)(el.val);
1568         assert(d.isNan == el.isNan);
1569         assert(d.isInf == el.isInf);
1570         assert(d.invalidOperation == el.invalid);
1571     }
1572 }
1573 
1574 // range construction
1575 @system pure
1576 unittest
1577 {
1578     import std.internal.test.dummyrange;
1579     auto r1 = new ReferenceForwardRange!dchar("123.456");
1580     auto d1 = Decimal!()(r1);
1581     assert(d1.coefficient == 123_456);
1582     assert(d1.sign == 0);
1583     assert(d1.exponent == -3);
1584 
1585     auto r2 = new ReferenceForwardRange!dchar("-0.00004");
1586     auto d2 = Decimal!()(r2);
1587     assert(d2.coefficient == 4);
1588     assert(d2.sign == 1);
1589     assert(d2.exponent == -5);
1590 }
1591 
1592 // int construction
1593 @system
1594 unittest
1595 {
1596     static struct Test
1597     {
1598         long val;
1599         ubyte sign;
1600         long coefficient;
1601     }
1602 
1603     static immutable testValues = [
1604         Test(10, 0, 10),
1605         Test(-10, 1, 10),
1606         Test(-1_000_000, 1, 1_000_000),
1607         Test(-147_483_648, 1, 147_483_648),
1608     ];
1609 
1610     foreach (el; testValues)
1611     {
1612         auto d = Decimal!()(el.val);
1613         assert(d.coefficient == el.coefficient);
1614         assert(d.sign == el.sign);
1615     }
1616 }
1617 
1618 // float construction
1619 @system
1620 unittest
1621 {
1622     static struct Test
1623     {
1624         double val;
1625         ubyte sign;
1626         long coefficient;
1627         long exponent;
1628     }
1629 
1630     static struct SpecialTest
1631     {
1632         double val;
1633         ubyte sign;
1634         bool isNan;
1635         bool isInf;
1636     }
1637 
1638     auto nonspecialTestValues = [
1639         Test(0.02, 0, 2, -2),
1640         Test(0.00002, 0, 2, -5),
1641         Test(1.02, 0, 102, -2),
1642         Test(200.0, 0, 200, 0),
1643         Test(1234.5678, 0, 12_345_678, -4),
1644         Test(-1234.5678, 1, 12_345_678, -4),
1645         Test(-1234, 1, 1234, 0),
1646         Test(int.min, 1, 2147483648, 0),
1647     ];
1648 
1649     auto specialTestValues = [
1650         SpecialTest(float.nan, 0, true, false),
1651         SpecialTest(-float.nan, 1, true, false),
1652         SpecialTest(float.infinity, 0, false, true),
1653         SpecialTest(-float.infinity, 1, false, true),
1654     ];
1655 
1656     foreach (el; nonspecialTestValues)
1657     {
1658         auto d = Decimal!()(el.val);
1659         assert(d.coefficient == el.coefficient);
1660         assert(d.sign == el.sign);
1661         assert(d.exponent == el.exponent);
1662 
1663         Decimal!() d2;
1664         d2 = el.val;
1665         assert(d2.coefficient == el.coefficient);
1666         assert(d2.sign == el.sign);
1667         assert(d2.exponent == el.exponent);
1668     }
1669 
1670     foreach (el; specialTestValues)
1671     {
1672         auto d = Decimal!()(el.val);
1673         assert(d.sign == el.sign);
1674         assert(d.isNan == el.isNan);
1675         assert(d.isInf == el.isInf);
1676 
1677         Decimal!() d2;
1678         d2 = el.val;
1679         assert(d2.sign == el.sign);
1680         assert(d2.isNan == el.isNan);
1681         assert(d2.isInf == el.isInf);
1682     }
1683 }
1684 
1685 // static ctors
1686 @system pure
1687 unittest
1688 {
1689     alias DType = Decimal!();
1690 
1691     auto d1 = DType.nan;
1692     assert(d1.isNan == true);
1693     auto d2 = DType.infinity;
1694     assert(d2.isInf == true);
1695 
1696     auto d3 = DType.max;
1697     assert(d3 == decimal("9999999999999999E999"));
1698     auto d4 = DType.min;
1699     assert(d4 == decimal("-1E-999"));
1700 }
1701 
1702 // addition and subtraction
1703 @system
1704 unittest
1705 {
1706     static struct Test
1707     {
1708         string val1;
1709         string val2;
1710         string expected;
1711         bool invalidOperation;
1712     }
1713 
1714     auto testPlusValues = [
1715         Test("-0", "-0", "-0"),
1716         Test("-0", "0", "0"),
1717         Test("1", "2", "3"),
1718         Test("-5", "-3", "-8"),
1719         Test("5.75", "3.3", "9.05"),
1720         Test("1.23456789", "1.00000000", "2.23456789"),
1721         Test("10E5", "10E4", "1100000"),
1722         Test("10E25", "10E4", "100000000000000000000000000"),
1723         Test("0.9998", "0.0000", "0.9998"),
1724         Test("1", "0.0001", "1.0001"),
1725         Test("1", "0.00", "1.00"),
1726         Test("123.00", "3000.00", "3123.00"),
1727         Test("123.00", "3000.00", "3123.00"),
1728         Test("1000", "NaN", "NaN"),
1729         Test("NaN", "NaN", "NaN"),
1730         Test("-NaN", "NaN", "-NaN"),
1731         Test("NaN", "Inf", "NaN"),
1732         Test("-NaN", "Inf", "-NaN"),
1733         Test("-Inf", "-Inf", "-Infinity"),
1734         Test("-Inf", "Inf", "NaN", true),
1735         Test("Inf", "-Inf", "NaN", true),
1736         Test("-Inf", "-1000", "-Infinity"),
1737         Test("-Inf", "1000", "-Infinity"),
1738         Test("Inf", "-1000", "Infinity"),
1739         Test("Inf", "1000", "Infinity")
1740     ];
1741 
1742     auto testMinusValues = [
1743         Test("-0", "0", "-0"),
1744         Test("0", "-0", "0"),
1745         Test("0", "0", "0"),
1746         Test("1.3", "0.3", "1.0"),
1747         Test("1.3", "2.07", "-0.77"),
1748         Test("1.25", "1.25", "0.00"),
1749         Test("3", "-3.0", "6.0"),
1750         Test("1.23456789", "1.00000000", "0.23456789"),
1751         Test("10.2345679", "10.2345675", "0.0000004"),
1752         Test("0.999999999", "1", "-0.000000001"),
1753         Test("2.000E-3", "1.00200", "-1.000000"),
1754         Test("-Inf", "Inf", "-Infinity"),
1755         Test("-Inf", "1000", "-Infinity"),
1756         Test("1000", "-Inf", "Infinity"),
1757         Test("NaN", "Inf", "NaN"),
1758         Test("Inf", "NaN", "NaN"),
1759         Test("NaN", "NaN", "NaN"),
1760         Test("-NaN", "NaN", "-NaN"),
1761         Test("Inf", "Inf", "NaN", true),
1762         Test("-Inf", "-Inf", "NaN", true),
1763     ];
1764 
1765     foreach (el; testPlusValues)
1766     {
1767         auto v1 = decimal!(NoOp)(el.val1);
1768         auto v2 = decimal(el.val2);
1769         auto res = v1 + v2;
1770         assert(res.toString() == el.expected);
1771         assert(res.invalidOperation == el.invalidOperation);
1772     }
1773 
1774     foreach (el; testMinusValues)
1775     {
1776         auto v1 = decimal!(NoOp)(el.val1);
1777         auto v2 = decimal(el.val2);
1778         auto res = v1 - v2;
1779         assert(res.toString() == el.expected);
1780         assert(res.invalidOperation == el.invalidOperation);
1781     }
1782 
1783     // check that float and int compile
1784     assert(decimal("2.22") + 0.01 == decimal("2.23"));
1785     assert(decimal("2.22") + 1 == decimal("3.22"));
1786 
1787     // test that opOpAssign works properly
1788     auto d1 = decimal("3.55");
1789     d1 += 0.45;
1790     assert(d1.toString() == "4.00");
1791     auto d2 = decimal("3.55");
1792     d2 -= 0.55;
1793     assert(d2.toString() == "3.00");
1794 
1795     static struct CustomHook
1796     {
1797         enum Rounding roundingMode = Rounding.HalfUp;
1798         enum uint precision = 3;
1799     }
1800 
1801     // rounding test on addition
1802     auto d3 = decimal!(CustomHook)("0.999E-2");
1803     auto d4 = decimal!(CustomHook)("0.1E-2");
1804     auto v = d3 + d4;
1805     assert(v.toString == "0.0110");
1806     assert(v.inexact);
1807     assert(v.rounded);
1808 
1809     // higher precision tests
1810     auto d5 = decimal!(HighPrecision)("10000e+9");
1811     auto d6 = decimal!(HighPrecision)("7");
1812     auto v2 = d5 - d6;
1813     assert(v2.toString() == "9999999999993");
1814 
1815     auto d7 = decimal!(HighPrecision)("1e-50");
1816     auto d8 = decimal!(HighPrecision)("4e-50");
1817     auto v3 = d7 + d8;
1818     assert(v3.toString() == "0.00000000000000000000000000000000000000000000000005");
1819 }
1820 
1821 // multiplication
1822 @system
1823 unittest
1824 {
1825     static struct Test
1826     {
1827         string val1;
1828         string val2;
1829         string expected;
1830         bool invalidOperation;
1831         bool rounded;
1832     }
1833 
1834     auto testValues = [
1835         Test("0", "0", "0"),
1836         Test("0", "-0", "-0"),
1837         Test("-0", "0", "-0"),
1838         Test("-0", "-0", "0"),
1839         Test("-00.00", "0E-3", "-0.00000"),
1840         Test("1.20", "3", "3.60"),
1841         Test("7", "3", "21"),
1842         Test("0.9", "0.8", "0.72"),
1843         Test("0.9", "-0", "-0.0"),
1844         Test("-1.20", "-2", "2.40"),
1845         Test("123.45", "1e7", "1234500000"),
1846         Test("12345", "10E-3", "123.450"),
1847         Test("1.23456789", "1.00000000", "1.234567890000000", false, true),
1848         Test("123456789", "10", "1234567890", false, false),
1849         Test("123456789", "100000000000000", "12345678900000000000000", false, true),
1850         Test("Inf", "-Inf", "-Infinity"),
1851         Test("-1000", "Inf", "-Infinity"),
1852         Test("Inf", "1000", "Infinity"),
1853         Test("0", "Inf", "NaN", true),
1854         Test("-Inf", "-Inf", "Infinity"),
1855         Test("Inf", "Inf", "Infinity"),
1856         Test("NaN", "Inf", "NaN"),
1857         Test("NaN", "-1000", "NaN"),
1858         Test("-NaN", "-1000", "-NaN"),
1859         Test("-NaN", "-NaN", "-NaN"),
1860         Test("-NaN", "-Inf", "-NaN"),
1861         Test("Inf", "-NaN", "-NaN")
1862     ];
1863 
1864     foreach (el; testValues)
1865     {
1866         auto d = decimal!(NoOp)(el.val1) * decimal!(NoOp)(el.val2);
1867         assert(d.toString() == el.expected);
1868         assert(d.invalidOperation == el.invalidOperation);
1869         assert(d.rounded == el.rounded);
1870     }
1871 
1872     // test that opOpAssign works properly
1873     auto d1 = decimal("2.5");
1874     d1 *= 5.4;
1875     assert(d1.toString() == "13.50");
1876 }
1877 
1878 // division
1879 @system
1880 unittest
1881 {
1882     import std.exception : assertThrown;
1883 
1884     static struct Test
1885     {
1886         string val1;
1887         string val2;
1888         string expected;
1889         bool divisionByZero;
1890         bool invalidOperation;
1891         bool inexact;
1892         bool rounded;
1893     }
1894 
1895     auto testValues = [
1896         Test("5", "2", "2.5"),
1897         Test("1", "10", "0.1"),
1898         Test("1", "4", "0.25"),
1899         Test("12", "12", "1"),
1900         Test("8.00", "2", "4.00"),
1901         Test("1000", "100", "10"),
1902         Test("2.40E+6", "2", "1200000"),
1903         Test("2.4", "-1", "-2.4"),
1904         Test("0.0", "1", "0.0"),
1905         Test("0.0", "-1", "-0.0"),
1906         Test("-0.0", "-1", "0.0"),
1907         Test("1", "3", "0.3333333333333333", false, false, true, true),
1908         Test("2", "3", "0.6666666666666667", false, false, true, true),
1909         Test("0", "0", "NaN", true, false),
1910         Test("1000", "0", "Infinity", true, true),
1911         Test("-1000", "0", "-Infinity", true, true),
1912         Test("Inf", "-Inf", "NaN", false, true),
1913         Test("-Inf", "Inf", "NaN", false, true),
1914         Test("Inf", "1000", "Infinity"),
1915         Test("Inf", "-1000", "-Infinity"),
1916         Test("1000", "Inf", "0"),
1917         Test("1000", "-Inf", "-0"),
1918         Test("Inf", "Inf", "NaN", false, true),
1919         Test("-Inf", "-Inf", "NaN", false, true),
1920         Test("NaN", "NaN", "NaN"),
1921         Test("-NaN", "NaN", "-NaN"),
1922         Test("NaN", "-Inf", "NaN"),
1923         Test("-NaN", "Inf", "-NaN"),
1924         Test("NaN", "-1000", "NaN"),
1925         Test("Inf", "NaN", "NaN"),
1926     ];
1927 
1928     foreach (el; testValues)
1929     {
1930         auto d = decimal!(NoOp)(el.val1) / decimal!(NoOp)(el.val2);
1931         assert(d.toString() == el.expected);
1932         assert(d.divisionByZero == el.divisionByZero);
1933         assert(d.invalidOperation == el.invalidOperation);
1934         assert(d.inexact == el.inexact);
1935         assert(d.rounded == el.rounded);
1936     }
1937 
1938     // test that opOpAssign works properly
1939     auto d1 = decimal(1000);
1940     d1 /= 10;
1941     assert(d1.toString() == "100");
1942 
1943     // test that the proper DivisionByZero function is called
1944     assertThrown!DivisionByZero(() { cast(void) (decimal!(Throw)(1) / decimal(0)); } ());
1945 }
1946 
1947 // cmp and equals
1948 @system
1949 unittest
1950 {
1951     static struct Test
1952     {
1953         string val1;
1954         string val2;
1955         int expected;
1956         bool invalidOperation;
1957     }
1958 
1959     auto testValues = [
1960         Test("inf", "0", 1),
1961         Test("-inf", "0", -1),
1962         Test("-inf", "-inf", 0),
1963         Test("inf", "-inf", 1),
1964         Test("-inf", "inf", -1),
1965         Test("NaN", "1000", -1),
1966         Test("-NaN", "1000", -1),
1967         Test("1000", "NAN", 1),
1968         Test("1000", "-NAN", 1),
1969         Test("NaN", "inf", -1),
1970         Test("-NaN", "inf", -1),
1971         Test("-NaN", "NaN", -1),
1972         Test("NaN", "-NaN", 1),
1973         Test("-NaN", "-NaN", 0),
1974         Test("NaN", "NaN", 0),
1975         Test("0", "-0", 0),
1976         Test("2.1", "3", -1),
1977         Test("2.1", "2.1", 0),
1978         Test("2.1", "2.10", 0),
1979         Test("3", "2.1", 1),
1980         Test("2.1", "-3", 1),
1981         Test("-3", "2.1", -1),
1982         Test("00", "00", 0),
1983         Test("70E-1", "7", 0),
1984         Test("8", "0.7E+1", 1),
1985         Test("-8.0", "7.0", -1),
1986         Test("80E-1", "-9", 1),
1987         Test("1E-15", "1", -1),
1988         Test("-0E2", "0", 0),
1989         Test("-8", "-70E-1", -1),
1990         Test("-12.1234", "-12.000000000", -1),
1991     ];
1992 
1993     foreach (el; testValues)
1994     {
1995         auto v1 = decimal!(NoOp)(el.val1);
1996         auto v2 = decimal!(NoOp)(el.val2);
1997         assert(v1.opCmp(v2) == el.expected);
1998         assert(v1.invalidOperation == el.invalidOperation);
1999     }
2000 
2001     // make sure equals compiles, already covered behavior in
2002     // cmp tests
2003     assert(decimal("19.9999") != decimal("21.222222"));
2004     assert(decimal("22.000") == decimal("22"));
2005     assert(decimal("22.000") == 22);
2006     assert(decimal("22.2") == 22.2);
2007 }
2008 
2009 // unary
2010 @system
2011 unittest
2012 {
2013     auto testPlusValues = [
2014         ["1", "1"],
2015         ["0", "0"],
2016         ["00.00", "0.00"],
2017         ["-2000000", "-2000000"],
2018         ["Inf", "Infinity"],
2019         ["NaN", "NaN"],
2020         ["-NaN", "-NaN"],
2021     ];
2022 
2023     auto testMinusValues = [
2024         ["1", "-1"],
2025         ["-1", "1"],
2026         ["0.00", "0.00"],
2027         ["-2000000", "2000000"],
2028         ["Inf", "-Infinity"],
2029         ["NaN", "NaN"],
2030         ["-NaN", "-NaN"]
2031     ];
2032 
2033     auto testPlusPlusValues = [
2034         ["1", "2"],
2035         ["-1", "0"],
2036         ["0.00", "1.00"],
2037         ["1.0000001", "2.0000001"],
2038         ["-2000000", "-1999999"],
2039         ["Inf", "Infinity"],
2040         ["-Inf", "-Infinity"],
2041         ["NaN", "NaN"],
2042         ["-NaN", "-NaN"]
2043     ];
2044 
2045     auto testMinusMinusValues = [
2046         ["1", "0"],
2047         ["-1", "-2"],
2048         ["1.00", "0.00"],
2049         ["1.0000001", "0.0000001"],
2050         ["-2000000", "-2000001"],
2051         ["Inf", "Infinity"],
2052         ["-Inf", "-Infinity"],
2053         ["NaN", "NaN"],
2054         ["-NaN", "-NaN"]
2055     ];
2056 
2057     foreach (el; testPlusValues)
2058     {
2059         auto d1 = decimal!(NoOp)(el[0]);
2060         auto d2 = +d1;
2061         assert(d2.toString == el[1]);
2062     }
2063     foreach (el; testMinusValues)
2064     {
2065         auto d1 = decimal!(NoOp)(el[0]);
2066         auto d2 = -d1;
2067         assert(d2.toString == el[1]);
2068     }
2069     foreach (el; testPlusPlusValues)
2070     {
2071         auto d1 = decimal!(NoOp)(el[0]);
2072         ++d1;
2073         assert(d1.toString == el[1]);
2074     }
2075     foreach (el; testMinusMinusValues)
2076     {
2077         auto d1 = decimal!(NoOp)(el[0]);
2078         --d1;
2079         assert(d1.toString == el[1]);
2080     }
2081 }
2082 
2083 // opCast
2084 @system
2085 unittest
2086 {
2087     import std.exception : assertThrown;
2088     import std.conv : ConvException, ConvOverflowException;
2089     import std.math : approxEqual, isNaN;
2090 
2091     assert((cast(bool) decimal("0.0")) == false);
2092     assert((cast(bool) decimal("0.5")) == false);
2093     assert((cast(bool) decimal("-0.5")) == false);
2094     assert((cast(bool) decimal("-1.0")) == true);
2095     assert((cast(bool) decimal("1.0")) == true);
2096     assert((cast(bool) decimal("1.1")) == true);
2097     assert((cast(bool) decimal("-1.1")) == true);
2098     assert((cast(bool) decimal("Infinity")) == true);
2099     assert((cast(bool) decimal("-Infinity")) == true);
2100     assert((cast(bool) decimal("-NaN")) == true);
2101     assert((cast(bool) decimal("NaN")) == true);
2102 
2103     assert((cast(real) decimal("0.0")).approxEqual(0));
2104     assert((cast(real) decimal("0.0123")).approxEqual(0.0123));
2105     assert((cast(real) decimal("123")).approxEqual(123.0));
2106     assert((cast(real) decimal("10.8888")).approxEqual(10.8888));
2107     assert(isNaN((cast(real) decimal("NaN"))));
2108     assert(isNaN((cast(real) decimal("-NaN"))));
2109     assert((cast(real) decimal("Inf")) == real.infinity);
2110     assert((cast(real) decimal("-Inf")) == -real.infinity);
2111 
2112     assert((cast(int) decimal("1")) == 1);
2113     assert((cast(int) decimal("1.0")) == 1);
2114     assert((cast(int) decimal("0.0")) == 0);
2115     assert((cast(int) decimal("-0")) == 0);
2116     assert((cast(int) decimal("-1")) == -1);
2117     assert((cast(int) decimal("0.0001")) == 0);
2118     assert((cast(int) decimal("10000.0001")) == 10000);
2119     assert((cast(ulong) decimal("-0")) == 0);
2120     assert((cast(ulong) decimal("0.0001")) == 0);
2121     assert((cast(ulong) decimal("10000.0001")) == 10000);
2122 
2123     assertThrown!(ConvOverflowException)((cast(ulong) decimal("-1")));
2124     assertThrown!(ConvException)((cast(ulong) decimal("INF")));
2125     assertThrown!(ConvException)((cast(ulong) decimal("NaN")));
2126 
2127     static struct CustomHook
2128     {
2129         enum Rounding roundingMode = Rounding.HalfUp;
2130         enum uint precision = 19;
2131     }
2132 
2133     assert((
2134         cast(real) decimal!(CustomHook)("12345678910111.213")
2135     ).approxEqual(12345678910111.213));
2136 
2137     assert((
2138         cast(real) decimal!(HighPrecision)("12345678910111213141516.1718192021")
2139     ).approxEqual(12345678910111213141516.1718192021));
2140 }
2141 
2142 // to string
2143 @system
2144 unittest
2145 {
2146     auto t = Decimal!()();
2147     t.sign = 0;
2148     t.coefficient = 2_708;
2149     t.exponent = -2;
2150     assert(t.toString() == "27.08");
2151 
2152     auto t2 = Decimal!()();
2153     t2.sign = 1;
2154     t2.coefficient = 1_953;
2155     t2.exponent = 0;
2156     assert(t2.toString() == "-1953");
2157 
2158     auto t3 = Decimal!()();
2159     t3.sign = 0;
2160     t3.coefficient = 9_888_555_555;
2161     t3.exponent = -4;
2162     assert(t3.toString() == "988855.5555");
2163 
2164     auto t4 = Decimal!()("300088.44");
2165     assert(t4.toString() == "300088.44");
2166 
2167     auto t5 = Decimal!()("30.5E10");
2168     assert(t5.toString() == "305000000000");
2169 
2170     auto t6 = Decimal!()(10);
2171     assert(t6.toString() == "10");
2172 
2173     auto t7 = Decimal!()(12_345_678);
2174     assert(t7.toString() == "12345678");
2175 
2176     auto t8 = Decimal!()(1234.5678);
2177     assert(t8.toString() == "1234.5678");
2178 
2179     auto t9 = Decimal!()(0.1234);
2180     assert(t9.toString() == "0.1234");
2181 
2182     auto t10 = Decimal!()(1234.0);
2183     assert(t10.toString() == "1234");
2184 
2185     auto t11 = Decimal!()("1.2345678E-7");
2186     assert(t11.toString() == "0.00000012345678");
2187 
2188     auto t12 = Decimal!()("INF");
2189     assert(t12.toString() == "Infinity");
2190 
2191     auto t13 = Decimal!()("-INF");
2192     assert(t13.toString() == "-Infinity");
2193 
2194     auto t14 = Decimal!()("NAN");
2195     assert(t14.toString() == "NaN");
2196 
2197     auto t15 = Decimal!()("-NAN");
2198     assert(t15.toString() == "-NaN");
2199 }
2200 
2201 // test hook setting enums
2202 @safe pure
2203 unittest
2204 {
2205     static assert(Decimal!(void).precision == 16);
2206     static assert(Decimal!(void).roundingMode == Rounding.HalfUp);
2207     static assert(Decimal!(void).maxExponent == 999);
2208     static assert(Decimal!(void).minExponent == -999);
2209 
2210     static struct A
2211     {
2212         enum maxExponent = 10;
2213     }
2214     static assert(Decimal!(A).precision == 16);
2215     static assert(Decimal!(A).roundingMode == Rounding.HalfUp);
2216     static assert(Decimal!(A).maxExponent == 10);
2217     static assert(Decimal!(A).minExponent == -999);
2218 
2219     static struct B
2220     {
2221         enum precision = 10;
2222         enum Rounding roundingMode = Rounding.Down;
2223         enum maxExponent = 10;
2224         enum minExponent = -10;
2225     }
2226     static assert(Decimal!(B).precision == 10);
2227     static assert(Decimal!(B).roundingMode == Rounding.Down);
2228     static assert(Decimal!(B).maxExponent == 10);
2229     static assert(Decimal!(B).minExponent == -10);
2230 }
2231 
2232 // test rounding
2233 @system
2234 unittest
2235 {
2236     import std.exception : assertThrown;
2237 
2238     static struct Test
2239     {
2240         ulong coefficient;
2241         ulong expected;
2242         bool inexact;
2243         bool rounded;
2244     }
2245 
2246     static struct DownHook
2247     {
2248         enum uint precision = 5;
2249         enum Rounding roundingMode = Rounding.Down;
2250     }
2251 
2252     static struct UpHook
2253     {
2254         enum uint precision = 5;
2255         enum Rounding roundingMode = Rounding.Up;
2256     }
2257 
2258     static struct HalfUpHook
2259     {
2260         enum uint precision = 5;
2261         enum Rounding roundingMode = Rounding.HalfUp;
2262     }
2263 
2264     auto downValues = [
2265         Test(12_345, 12345, false, false),
2266         Test(123_449, 12344, true, true),
2267         Test(1_234_499_999, 12_344, true, true),
2268         Test(123_451, 12_345, true, true),
2269         Test(123_450_000_001, 12_345, true, true),
2270         Test(1_234_649_999, 12_346, true, true),
2271         Test(123_465, 12_346, true, true),
2272         Test(1_234_650_001, 12_346, true, true),
2273         Test(1_234_500, 12_345, false, true)
2274     ];
2275     auto upValues = [
2276         Test(12_345, 12_345, false, false),
2277         Test(1_234_499, 12_345, true, true),
2278         Test(123_449_999_999, 12_345, true, true),
2279         Test(123_450_000_001, 12_346, true, true),
2280         Test(123_451, 12_346, true, true),
2281         Test(1_234_649_999, 12_347, true, true),
2282         Test(123_465, 12_347, true, true),
2283         Test(123_454, 12_346, true, true),
2284         Test(1_234_500, 12_345, false, true)
2285     ];
2286     auto halfUpValues = [
2287         Test(12_345, 12_345, false, false),
2288         Test(123_449, 12_345, true, true),
2289         Test(1_234_499, 12_345, true, true),
2290         Test(12_344_999, 12_345, true, true),
2291         Test(123_451, 12_345, true, true),
2292         Test(1_234_501, 12_345, true, true),
2293         Test(123_464_999, 12_346, true, true),
2294         Test(123_465, 12_347, true, true),
2295         Test(1_234_650_001, 12_347, true, true),
2296         Test(123_456, 12_346, true, true),
2297         Test(1_234_500, 12_345, false, true)
2298     ];
2299 
2300     foreach (e; downValues)
2301     {
2302         auto d = Decimal!(DownHook)(e.coefficient);
2303         assert(d.coefficient == e.expected);
2304         assert(d.rounded == e.rounded);
2305         assert(d.inexact == e.inexact);
2306     }
2307     foreach (e; upValues)
2308     {
2309         auto d = Decimal!(UpHook)(e.coefficient);
2310         assert(d.coefficient == e.expected);
2311         assert(d.rounded == e.rounded);
2312         assert(d.inexact == e.inexact);
2313     }
2314     foreach (e; halfUpValues)
2315     {
2316         auto d = Decimal!(HalfUpHook)(e.coefficient);
2317         assert(d.coefficient == e.expected);
2318         assert(d.rounded == e.rounded);
2319         assert(d.inexact == e.inexact);
2320     }
2321 
2322     // Test that the exponent is properly changed
2323     auto d1 = decimal!(HalfUpHook)("1.2345678E-7");
2324     assert(d1.exponent == -11);
2325 
2326     // test calling of defined hook methods
2327     static struct ThrowHook
2328     {
2329         enum uint precision = 5;
2330         enum Rounding roundingMode = Rounding.HalfUp;
2331 
2332         static void onRounded(T)(T d)
2333         {
2334             throw new Exception("Rounded");
2335         }
2336     }
2337 
2338     assertThrown!Exception(Decimal!(ThrowHook)(1_234_567));
2339 
2340     static struct HigherHook
2341     {
2342         enum uint precision = 16;
2343         enum Rounding roundingMode = Rounding.HalfUp;
2344     }
2345 
2346     static struct HigherHookDown
2347     {
2348         enum uint precision = 16;
2349         enum Rounding roundingMode = Rounding.Down;
2350     }
2351 
2352     static struct HigherHookUp
2353     {
2354         enum uint precision = 16;
2355         enum Rounding roundingMode = Rounding.Up;
2356     }
2357 
2358     auto d2 = decimal!(HigherHook)("10000000000000005");
2359     assert(d2.rounded);
2360     assert(d2.toString() == "10000000000000010");
2361 
2362     auto d3 = decimal!(HigherHookDown)("10000000000000005");
2363     assert(d3.rounded);
2364     assert(d3.toString() == "10000000000000000");
2365 
2366     auto d4 = decimal!(HigherHookUp)("10000000000000001");
2367     assert(d4.rounded);
2368     assert(d4.toString() == "10000000000000010");
2369 }
2370 
2371 // mixing of different precisions
2372 @system
2373 unittest
2374 {
2375     static struct CustomHook
2376     {
2377         enum Rounding roundingMode = Rounding.HalfUp;
2378         enum uint precision = 19;
2379     }
2380 
2381     auto d1 = decimal!(HighPrecision)("10000000000000000000");
2382     auto d2 = decimal!(HighPrecision)("120000000000.0000");
2383     auto d3 = decimal!(NoOp)("10000.00");
2384     auto d4 = decimal!(CustomHook)("120000000000.0000");
2385     auto d5 = d1 - d4;
2386     auto d6 = d4 - d1;
2387     auto d7 = d1 - d3;
2388     auto d8 = d1 * d4;
2389     auto d9 = d1 * d3;
2390     auto d10 = d4 * d1;
2391     auto d11 = d1 / d4;
2392     auto d12 = d4 / d1;
2393     auto d13 = d1 / d3;
2394 
2395     assert(d1.opCmp(d2) == 1);
2396     assert(d1.opCmp(d3) == 1);
2397     assert(d3.opCmp(d4) == -1);
2398     assert(d5 == decimal!(HighPrecision)("9999999880000000000.0000"));
2399     assert(d6 == decimal!(CustomHook)("-9999999880000000000.0000"));
2400     assert(d7 == decimal!(HighPrecision)("9999999999999990000.00"));
2401     assert(d8 == decimal!(HighPrecision)("1200000000000000000000000000000.0000"));
2402     assert(d9 == decimal!(HighPrecision)("100000000000000000000000.00"));
2403     assert(d10 == decimal!(CustomHook)("1200000000000000000000000000000"));
2404     assert(d11 == decimal!(HighPrecision)("83333333.33333333333333333333333333333333333333333333333333333333"));
2405     assert(d12 == decimal!(CustomHook)("0.000000012"));
2406     assert(d13 == decimal!(HighPrecision)("1000000000000000"));
2407 }
2408 
2409 /**
2410  * Factory function
2411  */
2412 auto decimal(Hook = Abort, R)(R r)
2413 if ((isForwardRange!R &&
2414     isSomeChar!(ElementEncodingType!R) &&
2415     !isInfinite!R) || isNumeric!R)
2416 {
2417     return Decimal!(Hook)(r);
2418 }
2419 
2420 ///
2421 @system
2422 unittest
2423 {
2424     auto d1 = decimal(5.5);
2425     assert(d1.toString == "5.5");
2426 
2427     auto d2 = decimal("500.555");
2428 }
2429 
2430 /**
2431  * Controls what happens when the number of significant digits exceeds `Hook.precision`
2432  */
2433 enum Rounding
2434 {
2435     /**
2436      * Round toward 0, a.k.a truncate. The discarded digits are ignored.
2437      */
2438     Down,
2439     /**
2440      * If the discarded digits represent greater than or equal to half (0.5)
2441      * of the value of a one in the next left position then the result coefficient
2442      * should be incremented by 1 (rounded up). Otherwise the discarded digits are ignored.
2443      */
2444     HalfUp,
2445     /**
2446      * If the discarded digits represent greater than half (0.5) the value of a
2447      * one in the next left position then the result coefficient should be
2448      * incremented by 1 (rounded up). If they represent less than half, then the
2449      * result coefficient is not adjusted (that is, the discarded digits are ignored).
2450      *
2451      * Otherwise (they represent exactly half) the result coefficient is unaltered
2452      * if its rightmost digit is even, or incremented by 1 (rounded up) if its
2453      * rightmost digit is odd (to make an even digit).
2454      */
2455     HalfEven,
2456     /**
2457      * If all of the discarded digits are zero or if the sign is 1 the result is
2458      * unchanged. Otherwise, the result coefficient should be incremented by 1
2459      * (rounded up).
2460      */
2461     Ceiling,
2462     /**
2463      * If all of the discarded digits are zero or if the sign is 0 the result is
2464      * unchanged. Otherwise, the sign is 1 and the result coefficient should be
2465      * incremented by 1.
2466      */
2467     Floor,
2468     /**
2469      * If the discarded digits represent greater than half (0.5) of the value of
2470      * a one in the next left position then the result coefficient should be
2471      * incremented by 1 (rounded up). Otherwise (the discarded digits are 0.5 or
2472      * less) the discarded digits are ignored.
2473      */
2474     HalfDown,
2475     /**
2476      * (Round away from 0.) If all of the discarded digits are zero the result is
2477      * unchanged. Otherwise, the result coefficient should be incremented by 1 (rounded up).
2478      */
2479     Up,
2480     /**
2481      * (Round zero or five away from 0.) The same as round-up, except that rounding
2482      * up only occurs if the digit to be rounded up is 0 or 5, and after overflow
2483      * the result is the same as for round-down.
2484      */
2485     ZeroFiveUp
2486 }
2487 
2488 /**
2489  * Will halt program on division by zero, invalid operations,
2490  * overflows, and underflows.
2491  *
2492  * Has 16 significant digits, rounds half up
2493  */
2494 struct Abort
2495 {
2496     ///
2497     enum Rounding roundingMode = Rounding.HalfUp;
2498     ///
2499     enum uint precision = 16;
2500     ///
2501     enum int maxExponent = 999;
2502     ///
2503     enum int minExponent = -999;
2504 
2505     ///
2506     static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T))
2507     {
2508         assert(0, "Division by zero");
2509     }
2510 
2511     ///
2512     static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T))
2513     {
2514         assert(0, "Invalid operation");
2515     }
2516 
2517     ///
2518     static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T))
2519     {
2520         assert(0, "Overflow");
2521     }
2522 
2523     ///
2524     static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T))
2525     {
2526         assert(0, "Underflow");
2527     }
2528 }
2529 
2530 /**
2531  * Same as abort, but offers 64 significant digits
2532  *
2533  * Note: As noted in the module overview, using 64 significant digits is
2534  * slower than `16` or `19`.
2535  */
2536 struct HighPrecision
2537 {
2538     ///
2539     enum Rounding roundingMode = Rounding.HalfUp;
2540     ///
2541     enum uint precision = 64;
2542     ///
2543     enum int maxExponent = 999;
2544     ///
2545     enum int minExponent = -999;
2546 
2547     ///
2548     static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T))
2549     {
2550         assert(0, "Division by zero");
2551     }
2552 
2553     ///
2554     static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T))
2555     {
2556         assert(0, "Invalid operation");
2557     }
2558 
2559     ///
2560     static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T))
2561     {
2562         assert(0, "Overflow");
2563     }
2564 
2565     ///
2566     static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T))
2567     {
2568         assert(0, "Underflow");
2569     }
2570 }
2571 
2572 /**
2573  * Will throw exceptions on division by zero, invalid operations,
2574  * overflows, and underflows
2575  *
2576  * Has 16 significant digits, rounds half up
2577  */
2578 struct Throw
2579 {
2580     ///
2581     enum Rounding roundingMode = Rounding.HalfUp;
2582     ///
2583     enum uint precision = 16;
2584     ///
2585     enum int maxExponent = 999;
2586     ///
2587     enum int minExponent = -999;
2588 
2589     ///
2590     static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T))
2591     {
2592         throw new DivisionByZero("Result: " ~ d.toString());
2593     }
2594 
2595     ///
2596     static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T))
2597     {
2598         throw new InvalidOperation("Result: " ~ d.toString());
2599     }
2600 
2601     ///
2602     static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T))
2603     {
2604         throw new Overflow("Result: " ~ d.toString());
2605     }
2606 
2607     ///
2608     static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T))
2609     {
2610         throw new Underflow("Result: " ~ d.toString());
2611     }
2612 }
2613 
2614 /**
2615  * Does nothing on invalid operations except the proper flags
2616  *
2617  * Has 16 significant digits, rounds half up
2618  */
2619 struct NoOp
2620 {
2621     ///
2622     enum Rounding roundingMode = Rounding.HalfUp;
2623     ///
2624     enum uint precision = 16;
2625     ///
2626     enum int maxExponent = 999;
2627     ///
2628     enum int minExponent = -999;
2629 }
2630 
2631 /**
2632  * Thrown when using $(LREF Throw) and division by zero occurs
2633  */
2634 class DivisionByZero : Exception
2635 {
2636     /++
2637         Params:
2638             msg  = The message for the exception.
2639             file = The file where the exception occurred.
2640             line = The line number where the exception occurred.
2641             next = The previous exception in the chain of exceptions, if any.
2642     +/
2643     this(string msg, string file = __FILE__, size_t line = __LINE__,
2644          Throwable next = null) @nogc @safe pure nothrow
2645     {
2646         super(msg, file, line, next);
2647     }
2648 
2649     /++
2650         Params:
2651             msg  = The message for the exception.
2652             next = The previous exception in the chain of exceptions.
2653             file = The file where the exception occurred.
2654             line = The line number where the exception occurred.
2655     +/
2656     this(string msg, Throwable next, string file = __FILE__,
2657          size_t line = __LINE__) @nogc @safe pure nothrow
2658     {
2659         super(msg, file, line, next);
2660     }
2661 }
2662 
2663 /**
2664  * Thrown when using $(LREF Throw) and an invalid operation occurs
2665  */
2666 class InvalidOperation : Exception
2667 {
2668     /++
2669         Params:
2670             msg  = The message for the exception.
2671             file = The file where the exception occurred.
2672             line = The line number where the exception occurred.
2673             next = The previous exception in the chain of exceptions, if any.
2674     +/
2675     this(string msg, string file = __FILE__, size_t line = __LINE__,
2676          Throwable next = null) @nogc @safe pure nothrow
2677     {
2678         super(msg, file, line, next);
2679     }
2680 
2681     /++
2682         Params:
2683             msg  = The message for the exception.
2684             next = The previous exception in the chain of exceptions.
2685             file = The file where the exception occurred.
2686             line = The line number where the exception occurred.
2687     +/
2688     this(string msg, Throwable next, string file = __FILE__,
2689          size_t line = __LINE__) @nogc @safe pure nothrow
2690     {
2691         super(msg, file, line, next);
2692     }
2693 }
2694 
2695 /**
2696  * Thrown when using $(LREF Throw) and overflow occurs
2697  */
2698 class Overflow : Exception
2699 {
2700     /++
2701         Params:
2702             msg  = The message for the exception.
2703             file = The file where the exception occurred.
2704             line = The line number where the exception occurred.
2705             next = The previous exception in the chain of exceptions, if any.
2706     +/
2707     this(string msg, string file = __FILE__, size_t line = __LINE__,
2708          Throwable next = null) @nogc @safe pure nothrow
2709     {
2710         super(msg, file, line, next);
2711     }
2712 
2713     /++
2714         Params:
2715             msg  = The message for the exception.
2716             next = The previous exception in the chain of exceptions.
2717             file = The file where the exception occurred.
2718             line = The line number where the exception occurred.
2719     +/
2720     this(string msg, Throwable next, string file = __FILE__,
2721          size_t line = __LINE__) @nogc @safe pure nothrow
2722     {
2723         super(msg, file, line, next);
2724     }
2725 }
2726 
2727 /**
2728  * Thrown when using $(LREF Throw) and underflow occurs
2729  */
2730 class Underflow : Exception
2731 {
2732     /++
2733         Params:
2734             msg  = The message for the exception.
2735             file = The file where the exception occurred.
2736             line = The line number where the exception occurred.
2737             next = The previous exception in the chain of exceptions, if any.
2738     +/
2739     this(string msg, string file = __FILE__, size_t line = __LINE__,
2740          Throwable next = null) @nogc @safe pure nothrow
2741     {
2742         super(msg, file, line, next);
2743     }
2744 
2745     /++
2746         Params:
2747             msg  = The message for the exception.
2748             next = The previous exception in the chain of exceptions.
2749             file = The file where the exception occurred.
2750             line = The line number where the exception occurred.
2751     +/
2752     this(string msg, Throwable next, string file = __FILE__,
2753          size_t line = __LINE__) @nogc @safe pure nothrow
2754     {
2755         super(msg, file, line, next);
2756     }
2757 }
2758 
2759 /// Returns: If this decimal represents a positive or negative NaN
2760 bool isNaN(D)(D d) if (isInstanceOf!(Decimal, D))
2761 {
2762     return d.isNan;
2763 }
2764 
2765 ///
2766 @system unittest
2767 {
2768     assert( isNaN(decimal("NaN")));
2769     assert( isNaN(decimal("-NaN")));
2770     assert(!isNaN(decimal("Inf")));
2771     assert(!isNaN(decimal("1.001")));
2772 }
2773 
2774 /// Returns: If this decimal represents positive or negative infinity
2775 bool isInfinity(D)(D d) if (isInstanceOf!(Decimal, D))
2776 {
2777     return d.isInf;
2778 }
2779 
2780 ///
2781 @system unittest
2782 {
2783     assert( isInfinity(decimal("Inf")));
2784     assert( isInfinity(decimal("-Inf")));
2785     assert(!isInfinity(decimal("NaN")));
2786     assert(!isInfinity(decimal("1.001")));
2787 }
2788 
2789 /// Returns: The given decimal with a positive sign
2790 auto abs(D)(D d) if (isInstanceOf!(Decimal, D))
2791 {
2792     if (d.sign == 1)
2793         return -d;
2794     return d;
2795 }
2796 
2797 ///
2798 unittest
2799 {
2800     assert(abs(decimal("1.00")) == decimal("1.00"));
2801     assert(abs(decimal("-1.00")) == decimal("1.00"));
2802     assert(abs(decimal("-0.0002")) == decimal("0.0002"));
2803 }
2804 
2805 private:
2806 
2807 /*
2808  * Get the number of digits in the decimal representation of a number
2809  */
2810 auto numberOfDigits(T)(T x)
2811 {
2812     if (x < 0)
2813         x *= -1;
2814 
2815     immutable len = x.ulongLength;
2816 
2817     if (len == 1)
2818     {
2819         if (x == 0UL) return 1;
2820         if (x < 10UL) return 1;
2821         if (x < 100UL) return 2;
2822         if (x < 1_000UL) return 3;
2823         if (x < 10_000UL) return 4;
2824         if (x < 100_000UL) return 5;
2825         if (x < 1_000_000UL) return 6;
2826         if (x < 10_000_000UL) return 7;
2827         if (x < 100_000_000UL) return 8;
2828         if (x < 1_000_000_000UL) return 9;
2829         if (x < 10_000_000_000UL) return 10;
2830         if (x < 100_000_000_000UL) return 11;
2831         if (x < 1_000_000_000_000UL) return 12;
2832         if (x < 10_000_000_000_000UL) return 13;
2833         if (x < 100_000_000_000_000UL) return 14;
2834         if (x < 1_000_000_000_000_000UL) return 15;
2835         if (x < 10_000_000_000_000_000UL) return 16;
2836         if (x < 100_000_000_000_000_000UL) return 17;
2837         if (x < 1_000_000_000_000_000_000UL) return 18;
2838     }
2839 
2840     uint digits = 19;
2841     BigInt num = BigInt(10_000_000_000_000_000_000UL);
2842 
2843     if (len == 3)
2844     {
2845         digits = 39;
2846         version(D_InlineAsm_X86)
2847         {
2848             num *= 10000000000000000000UL;
2849             num *= 10UL;
2850         }
2851         else
2852         {
2853             enum BigInt lentwo = BigInt("100000000000000000000");
2854             num *= lentwo;
2855         }
2856     }
2857     else if (len == 4)
2858     {
2859         digits = 58;
2860         version(D_InlineAsm_X86)
2861         {
2862             num *= 10000000000000000000UL;
2863             num *= 10000000000000000000UL;
2864             num *= 10UL;
2865         }
2866         else
2867         {
2868             enum BigInt lenthree = BigInt("1000000000000000000000000000000000000000");
2869             num *= lenthree;
2870         }
2871     }
2872     else if (len > 4)
2873     {
2874         digits = 78;
2875         version(D_InlineAsm_X86)
2876         {
2877             num *= 10000000000000000000UL;
2878             num *= 10000000000000000000UL;
2879             num *= 10000000000000000000UL;
2880             num *= 100UL;
2881         }
2882         else
2883         {
2884             enum BigInt lenfour = BigInt("100000000000000000000000000000000000000000000000000000000000");
2885             num *= lenfour;
2886         }
2887     }
2888 
2889     for (;; num *= 10, digits++)
2890     {
2891         if (x < num)
2892             return digits;
2893     }
2894 }
2895 
2896 @system pure
2897 unittest
2898 {
2899     import std.bigint;
2900     import std.range : chain, repeat;
2901     import std.utf : byCodeUnit;
2902 
2903     assert(numberOfDigits(BigInt("0")) == 1);
2904     assert(numberOfDigits(BigInt("1")) == 1);
2905     assert(numberOfDigits(BigInt("1_000")) == 4);
2906     assert(numberOfDigits(BigInt("-1_000")) == 4);
2907     assert(numberOfDigits(BigInt("1_000_000")) == 7);
2908     assert(numberOfDigits(BigInt("123_456")) == 6);
2909     assert(numberOfDigits(BigInt("123_456")) == 6);
2910     assert(numberOfDigits(BigInt("123_456_789_101_112_131_415_161")) == 24);
2911     assert(numberOfDigits(BigInt("1_000_000_000_000_000_000_000_000")) == 25);
2912     assert(numberOfDigits(BigInt("1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000")) == 46);
2913     assert(numberOfDigits(BigInt("1".byCodeUnit.chain('0'.repeat(60)))) == 61);
2914     assert(numberOfDigits(BigInt("1".byCodeUnit.chain('0'.repeat(99)))) == 100);
2915 }
2916 
2917 /*
2918  * Detect whether $(D X) is an enum type, or manifest constant.
2919  */
2920 template isEnum(X...) if (X.length == 1)
2921 {
2922     static if (is(X[0] == enum))
2923     {
2924         enum isEnum = true;
2925     }
2926     else static if (!is(X[0]) &&
2927                     !is(typeof(X[0]) == void) &&
2928                     !isFunction!(X[0]))
2929     {
2930         enum isEnum =
2931             !is(typeof({ auto ptr = &X[0]; }))
2932          && !is(typeof({ enum off = X[0].offsetof; }));
2933     }
2934     else
2935         enum isEnum = false;
2936 }