1 /******
2  * Handling amounts of money safely and efficiently.
3  *
4  * An amount of money is a number tagged with a currency id like "EUR"
5  * or "USD". Precision and rounding mode can be chosen as template
6  * parameters.
7  *
8  * If you write code which handles money, you have to choose a data type
9  * for it. Out of the box, D offers you floating point, integer, and
10  * std.bigint. All of these have their problems.
11  *
12  * Floating point is inherently imprecise. If your dollar numbers become
13  * too big, then you start getting too much or too little cents. This
14  * is not acceptable as the errors accumulate. Also, floating point has
15  * values like "infinity" and "not a number" and if those show up,
16  * usually things break, if you did not prepare for it. Debugging then
17  * means to work backwards how this happened, which is tedious and hard.
18  *
19  * Integer numbers do not suffer from imprecision, but they can not
20  * represent numbers as big as floating point. Worse, if your numbers
21  * become too big, then your CPU silently wraps them into negative
22  * numbers. Like the imprecision with floating point, your data is
23  * now corrupted without anyone noticing it yet. Also, fixed point
24  * arithmetic with integers is easy to get wrong and you need a
25  * fractional part to represent cents, for example.
26  *
27  * As a third option, there is std.bigint, which provides numbers
28  * with arbitrary precision. Like floating point, the arithmetic is easy.
29  * Like integer, precision is fine. The downside is performance.
30  * Nevertheless, from the three options, this is the most safe one.
31  *
32  * Can we do even better?
33  * If we design a custom data type for money, we can improve safety
34  * even more. For example, certain arithmetics can be forbidden. What
35  * does it mean to multiply two money amounts, for example? There is no
36  * such thing as $² which makes any sense. However, you can certainly
37  * multiply a money amount with a unitless number. A custom data type
38  * can precisely allow and forbid this operations.
39  *
40  * Here the design decision is to use an integer for the internal
41  * representation. This limits the amounts you can use. For example,
42  * if you decide to use 4 digits behind the comma, the maximum number
43  * is 922,337,203,685,477.5807 or roughly 922 trillion. The US debt is
44  * currently in the trillions, so there are certainly cases where
45  * this representation is not applicable. However, we can check overflow,
46  * so if it happens, you get an exception thrown and notice it
47  * right away. The upside of using an integer is performance and
48  * a deterministic arithmetic all programmers are familiar with.
49  *
50  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
51  * Authors: Andreas Zwinkau
52  */
53 module money;
54 
55 import std.math : floor, ceil, lrint, abs, FloatingPointControl;
56 import std.conv : to;
57 import core.checkedint : adds, subs, muls, negs;
58 import std.format : FormatSpec, formattedWrite;
59 import std.traits : hasMember;
60 
61 @nogc pure @safe nothrow private long pow10(int x)
62 {
63     if (x <= 0)
64         return 1;
65     return 10 * pow10(x - 1);
66 }
67 
68 pure @safe nothrow private string decimals_format(int x)()
69 {
70     import std.conv : text;
71 
72     return "%0" ~ text(x) ~ "d";
73 }
74 
75 /** Holds an amount of currency **/
76 struct currency(string currency_name, int dec_places = 4, roundingMode rmode = roundingMode.HALF_UP)
77 {
78     alias T = typeof(this);
79     enum __currency = currency_name;
80     enum __dec_places = dec_places;
81     enum __rmode = rmode;
82     long amount;
83 
84     /// Floating point contructor. Uses rmode on x.
85     this(double x)
86     {
87         amount = to!long(round(x * pow10(dec_places), rmode));
88     }
89 
90     /** String contructor.
91       *
92       * Throws: ParseError or std.conv.ConvOverflowException for invalid inputs
93       */
94     this(string x)
95     {
96         import std.regex;
97 
98         auto match = matchFirst(x, ctRegex!"(-?)([0-9]+).?([0-9]*)");
99         if (match.length == 0)
100             throw new ParseError("Does not start with digit or minus: " ~ x);
101         long integer = match[2].to!long;
102         long decimals;
103         if (match[3] != "")
104             decimals = match[3].to!long;
105         if (long.max / pow10(dec_places) < integer)
106             throw new ParseError("Number too large: " ~ x);
107         auto dec_amount = decimals * pow10(cast(int)(dec_places - match[3].length));
108         amount = integer * pow10(dec_places) + dec_amount;
109         if (match[1] == "-")
110             amount = -amount;
111     }
112 
113     private static T fromLong(long a)
114     {
115         T ret = void;
116         ret.amount = a;
117         return ret;
118     }
119 
120     /// default initialisation value is zero
121     static immutable init = fromLong(0L);
122     /// maximum amount depends on dec_places
123     static immutable max = fromLong(long.max);
124     /// minimum amount depends on dec_places
125     static immutable min = fromLong(long.min);
126 
127     private static immutable dec_mask = pow10(dec_places);
128 
129     /// Can add and subtract money amounts of the same type.
130     T opBinary(string op)(const T rhs) const
131     {
132         static if (op == "+")
133         {
134             bool overflow;
135             auto ret = fromLong(adds(amount, rhs.amount, overflow));
136             if (overflow)
137                 throw new OverflowException();
138             return ret;
139         }
140         else static if (op == "-")
141         {
142             bool overflow;
143             auto ret = fromLong(subs(amount, rhs.amount, overflow));
144             if (overflow)
145                 throw new OverflowException();
146             return ret;
147         }
148         else
149             static assert(0, "Operator " ~ op ~ " not implemented");
150     }
151 
152     /// Can multiply, divide, and modulo with integer values.
153     T opBinary(string op)(const long rhs) const
154     {
155         static if (op == "*")
156         {
157             bool overflow;
158             auto ret = fromLong(muls(amount, rhs, overflow));
159             if (overflow)
160                 throw new OverflowException();
161             return ret;
162         }
163         else static if (op == "/")
164         {
165             return fromLong(amount / rhs);
166         }
167         else static if (op == "%")
168         {
169             const intpart = amount / pow10(dec_places);
170             return fromLong(intpart % rhs * pow10(dec_places));
171         }
172         else
173             static assert(0, "Operator " ~ op ~ " not implemented");
174     }
175 
176     /// Can multiply, divide, and modulo floating point numbers.
177     T opBinary(string op)(const real rhs) const
178     {
179         static if (op == "*")
180         {
181             const converted = T(rhs);
182             bool overflow = false;
183             const result = muls(amount, converted.amount, overflow);
184             if (overflow)
185                 throw new OverflowException();
186             return fromLong(result / pow10(dec_places));
187         }
188         else static if (op == "/")
189         {
190             const converted = T(rhs);
191             bool overflow = false;
192             auto mult = muls(amount, pow10(dec_places), overflow);
193             if (overflow)
194                 throw new OverflowException();
195             return fromLong(mult / converted.amount);
196         }
197         else static if (op == "%")
198         {
199             const converted = T(rhs);
200             return fromLong(amount % converted.amount);
201         }
202         else
203             static assert(0, "Operator " ~ op ~ " not implemented");
204     }
205 
206     /// Can add and subtract money amounts of the same type.
207     void opOpAssign(string op)(const T rhs)
208     {
209         static if (op == "+")
210         {
211             bool overflow;
212             auto ret = adds(amount, rhs.amount, overflow);
213             if (overflow)
214                 throw new OverflowException();
215             amount = ret;
216         }
217         else static if (op == "-")
218         {
219             bool overflow;
220             auto ret = subs(amount, rhs.amount, overflow);
221             if (overflow)
222                 throw new OverflowException();
223             amount = ret;
224         }
225         else
226             static assert(0, "Operator " ~ op ~ " not implemented");
227     }
228 
229     /// Can multiply, divide, and modulo with integer values.
230     void opOpAssign(string op)(const long rhs)
231     {
232         static if (op == "*")
233         {
234             bool overflow;
235             auto ret = muls(amount, rhs, overflow);
236             if (overflow)
237                 throw new OverflowException();
238             amount = ret;
239         }
240         else static if (op == "/")
241         {
242             amount /= rhs;
243         }
244         else static if (op == "%")
245         {
246             const intpart = amount / pow10(dec_places);
247             amount = intpart % rhs * pow10(dec_places);
248         }
249         else
250             static assert(0, "Operator " ~ op ~ " not implemented");
251     }
252 
253     /// Can multiply, divide, and modulo floating point numbers.
254     void opOpAssign(string op)(const real rhs)
255     {
256         static if (op == "*")
257         {
258             const converted = T(rhs);
259             bool overflow = false;
260             const result = muls(amount, converted.amount, overflow);
261             if (overflow)
262                 throw new OverflowException();
263             amount = result / pow10(dec_places);
264         }
265         else static if (op == "/")
266         {
267             const converted = T(rhs);
268             bool overflow = false;
269             auto mult = muls(amount, pow10(dec_places), overflow);
270             if (overflow)
271                 throw new OverflowException();
272             amount = mult / converted.amount;
273         }
274         else static if (op == "%")
275         {
276             const converted = T(rhs);
277             amount = amount % converted.amount;
278         }
279         else
280             static assert(0, "Operator " ~ op ~ " not implemented");
281     }
282 
283     /// Can check equality with money amounts of the same concurrency and decimal places.
284     bool opEquals(OT)(auto ref const OT other) const 
285             if (isCurrency!OT && other.__currency == currency_name
286                 && other.__dec_places == dec_places)
287     {
288         return other.amount == amount;
289     }
290 
291     /// Can compare with money amounts of the same concurrency.
292     int opCmp(OT)(const OT other) const 
293             if (isCurrency!OT && other.__currency == currency_name)
294     {
295         static if (dec_places == other.__dec_places)
296         {
297             if (other.amount > this.amount)
298                 return -1;
299             if (other.amount < this.amount)
300                 return 1;
301             return 0;
302         }
303         else static if (dec_places < other.__dec_places)
304         {
305             /* D implicitly makes this work for case '>' */
306             auto nthis = this * pow10(other.__dec_places - dec_places);
307             /* overflow check included */
308             if (other.amount > nthis.amount)
309                 return -1;
310             if (other.amount < nthis.amount)
311                 return 1;
312             return 0;
313         }
314         else
315             static assert(0, "opCmp with such 'other' not implemented");
316     }
317 
318     void toDecimalString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const
319     {
320         formattedWrite(sink, "%d", (amount / dec_mask));
321         sink(".");
322         auto decimals = abs(amount % dec_mask);
323         if (fmt.precision < dec_places)
324         {
325             auto n = dec_places - fmt.precision;
326             decimals = round!(rmode)(decimals, n);
327             decimals = decimals / pow10(n);
328             import std.conv : text;
329 
330             formattedWrite(sink, "%0" ~ text(fmt.precision) ~ "d", decimals);
331         }
332         else
333         {
334             formattedWrite(sink, decimals_format!dec_places(), decimals);
335         }
336     }
337 
338     /// Can convert to string.
339     void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const
340     {
341         switch (fmt.spec)
342         {
343         case 's': /* default e.g. for writeln */
344             goto case;
345         case 'f':
346 	        toDecimalString(sink, fmt);
347             sink(currency_name);
348             break;
349         case 'F':
350 	        toDecimalString(sink, fmt);
351             break;
352         case 'd':
353             auto ra = round!rmode(amount, dec_places);
354             formattedWrite(sink, "%d", (ra / dec_mask));
355             sink(currency_name);
356             break;
357         default:
358             throw new Exception("Unknown format specifier: %" ~ fmt.spec);
359         }
360     }
361 }
362 
363 /// Basic usage
364 unittest
365 {
366     alias EUR = currency!("EUR");
367     assert(EUR(100.0001) == EUR(100.00009));
368     assert(EUR(3.10) + EUR(1.40) == EUR(4.50));
369     assert(EUR(3.10) - EUR(1.40) == EUR(1.70));
370     assert(EUR(10.01) * 1.1 == EUR(11.011));
371 
372     import std.format : format;
373 
374     // for writefln("%d", EUR(3.6));
375     assert(format("%d", EUR(3.6)) == "4EUR");
376     assert(format("%d", EUR(3.1)) == "3EUR");
377     // for writefln("%f", EUR(3.141592));
378     assert(format("%f", EUR(3.141592)) == "3.1416EUR");
379     assert(format("%.2f", EUR(3.145)) == "3.15EUR");
380     // From issue #5
381     assert(format("%.4f", EUR(0.01234)) == "0.0123EUR");
382     
383     assert(format("%F", EUR(3.141592)) == "3.1416");
384 }
385 
386 /// Overflow is an error, since silent corruption is worse
387 @safe unittest
388 {
389     import std.exception : assertThrown;
390 
391     alias EUR = currency!("EUR");
392     auto one = EUR(1);
393     assertThrown!OverflowException(EUR.max + one);
394     assertThrown!OverflowException(EUR.min - one);
395 }
396 
397 /// Arithmetic ignores rounding mode
398 @safe unittest
399 {
400     alias EUR = currency!("EUR", 2, roundingMode.UP);
401     auto one = EUR(1);
402     assert(one != one / 3);
403 }
404 
405 /// Generic equality and order
406 @safe unittest
407 {
408     alias USD = currency!("USD", 2);
409     alias EURa = currency!("EUR", 2);
410     alias EURb = currency!("EUR", 4);
411     alias EURc = currency!("EUR", 4, roundingMode.DOWN);
412     // cannot compile with different currencies
413     static assert(!__traits(compiles, EURa(1) == USD(1)));
414     // cannot compile with different dec_places
415     static assert(!__traits(compiles, EURa(1) == EURb(1)));
416     // can check equality if only rounding mode differs
417     assert(EURb(1.01) == EURc(1.01));
418     // cannot compare with different currencies
419     static assert(!__traits(compiles, EURa(1) < USD(1)));
420 }
421 
422 // TODO Using negative dec_places for big numbers?
423 //@nogc @safe unittest
424 //{
425 //    alias USD = currency!("USD", -6);
426 //    assert(USD(1_000_000.00) == USD(1_100_000.));
427 //}
428 
429 enum isCurrency(T) = (hasMember!(T, "amount") && hasMember!(T,
430             "__dec_places") && hasMember!(T, "__rmode"));
431 static assert(isCurrency!(currency!"EUR"));
432 
433 // TODO @safe (due to std.format.format)
434 unittest
435 {
436     alias EUR = currency!("EUR");
437     import std.format : format;
438 
439     assert(format("%s", EUR(3.1)) == "3.1000EUR");
440 
441     import std.exception : assertThrown;
442 
443     assertThrown!Exception(format("%x", EUR(3.1)));
444 }
445 
446 @safe unittest
447 {
448     alias EUR = currency!("EUR");
449     assert(EUR(5) < EUR(6));
450     assert(EUR(6) > EUR(5));
451     assert(EUR(5) >= EUR(5));
452     assert(EUR(5) == EUR(5));
453     assert(EUR(6) != EUR(5));
454 
455     import std.exception : assertThrown;
456 
457     assertThrown!OverflowException(EUR.max * 2);
458     assertThrown!OverflowException(EUR.max * 2.0);
459 }
460 
461 @safe unittest
462 {
463     alias EUR = currency!("EUR");
464     auto x = EUR(42);
465     assert(EUR(84) == x * 2);
466     static assert(!__traits(compiles, x * x));
467     assert(EUR(21) == x / 2);
468     assert(EUR(2) == x % 4);
469 }
470 
471 @safe unittest
472 {
473     alias EURa = currency!("EUR", 2);
474     alias EURb = currency!("EUR", 4);
475     auto x = EURa(1.01);
476     assert(x > EURb(1.0001));
477     assert(x < EURb(1.0101));
478     assert(x <= EURb(1.01));
479 }
480 
481 @safe unittest
482 {
483     alias EUR = currency!("EUR");
484     auto x = EUR(2.22);
485     x += EUR(2.22);
486     assert(x == EUR(4.44));
487     x -= EUR(3.33);
488     assert(x == EUR(1.11));
489     x *= 4;
490     assert(x == EUR(4.44));
491     x /= 2;
492     assert(x == EUR(2.22));
493     x *= 2.0;
494     assert(x == EUR(4.44));
495     x /= 2.0;
496     assert(x == EUR(2.22));
497     x %= 3.0;
498     assert(x == EUR(2.22));
499     x %= 3;
500     assert(x == EUR(2));
501 }
502 
503 @safe unittest
504 {
505     import std.exception : assertThrown;
506 
507     alias EUR = currency!("EUR");
508     EUR x = EUR.max;
509     EUR y = EUR.min;
510     assertThrown!OverflowException(x += EUR(1));
511     assert(x == EUR.max);
512     assertThrown!OverflowException(y -= EUR(1));
513     assert(y == EUR.min);
514     assertThrown!OverflowException(x *= 2);
515     assert(x == EUR.max);
516     assertThrown!OverflowException(x *= 2.0);
517     assert(x == EUR.max);
518     assertThrown!OverflowException(y /= 10.0);
519     assert(y == EUR.min);
520 }
521 
522 /** Specifies rounding behavior **/
523 enum roundingMode
524 {
525     // dfmt off
526     /** Round upwards, e.g. 3.1 up to 4. */
527     UP,
528     /** Round downwards, e.g. 3.9 down to 3. */
529     DOWN,
530     /** Round to nearest number, half way between round up, e.g. 3.5 to 4. */
531     HALF_UP,
532     /** Round to nearest number, half way between round dow, e.g. 3.5 to 3.  */
533     HALF_DOWN,
534     /** Round to nearest number, half way between round to even number, e.g. 3.5 to 4. */
535     HALF_EVEN,
536     /** Round to nearest number, half way between round to odd number, e.g. 3.5 to 3. */
537     HALF_ODD,
538     /** Round to nearest number, half way between round towards zero, e.g. -3.5 to -3.  */
539     HALF_TO_ZERO,
540     /** Round to nearest number, half way between round away from zero, e.g. -3.5 to -4.  */
541     HALF_FROM_ZERO,
542     /** Throw exception if rounding would be necessary */
543     UNNECESSARY
544     // dfmt on
545 }
546 
547 /** Round an integer to a certain decimal place according to rounding mode */
548 long round(roundingMode m)(long x, int dec_place)
549 out (result)
550 {
551     assert((result % pow10(dec_place)) == 0);
552 }
553 body
554 {
555     const zeros = pow10(dec_place);
556     /* short cut, also removes edge cases */
557     if ((x % zeros) == 0)
558         return x;
559 
560     const half = zeros / 2;
561     with (roundingMode)
562     {
563         static if (m == UP)
564         {
565             return ((x / zeros) + 1) * zeros;
566         }
567         else static if (m == DOWN)
568         {
569             return x / zeros * zeros;
570         }
571         else static if (m == HALF_UP)
572         {
573             if ((x % zeros) >= half)
574                 return ((x / zeros) + 1) * zeros;
575             else
576                 return x / zeros * zeros;
577         }
578         else static if (m == HALF_DOWN)
579         {
580             if ((x % zeros) > half)
581                 return ((x / zeros) + 1) * zeros;
582             else
583                 return x / zeros * zeros;
584         }
585         else static if (m == HALF_EVEN)
586         {
587             const down = x / zeros;
588             if (down % 2 == 0)
589                 return down * zeros;
590             else
591                 return (down + 1) * zeros;
592         }
593         else static if (m == HALF_ODD)
594         {
595             const down = x / zeros;
596             if (down % 2 == 0)
597                 return (down + 1) * zeros;
598             else
599                 return down * zeros;
600         }
601         else static if (m == HALF_TO_ZERO)
602         {
603             const down = x / zeros;
604             if (down < 0)
605             {
606                 if (abs(x % zeros) <= half)
607                 {
608                     return (down) * zeros;
609                 }
610                 else
611                 {
612                     return (down - 1) * zeros;
613                 }
614             }
615             else
616             {
617                 if ((x % zeros) > half)
618                 {
619                     return (down + 1) * zeros;
620                 }
621                 else
622                 {
623                     return (down) * zeros;
624                 }
625             }
626         }
627         else static if (m == HALF_FROM_ZERO)
628         {
629             const down = x / zeros;
630             if (down < 0)
631             {
632                 if (abs(x % zeros) < half)
633                 {
634                     return (down) * zeros;
635                 }
636                 else
637                 {
638                     return (down - 1) * zeros;
639                 }
640             }
641             else
642             {
643                 if (x % zeros >= half)
644                 {
645                     return (down + 1) * zeros;
646                 }
647                 else
648                 {
649                     return (down) * zeros;
650                 }
651             }
652         }
653         else static if (m == UNNECESSARY)
654         {
655             throw new ForbiddenRounding();
656         }
657     }
658 }
659 
660 // dfmt off
661 ///
662 @nogc @safe unittest
663 {
664     assert (round!(roundingMode.DOWN)     (1009, 1) == 1000);
665     assert (round!(roundingMode.UP)       (1001, 1) == 1010);
666     assert (round!(roundingMode.HALF_UP)  (1005, 1) == 1010);
667     assert (round!(roundingMode.HALF_DOWN)(1005, 1) == 1000);
668 }
669 // dfmt on
670 
671 @safe pure @nogc nothrow unittest
672 {
673     // dfmt off
674     assert (round!(roundingMode.HALF_UP)       ( 10, 1) ==  10);
675     assert (round!(roundingMode.UP)            ( 11, 1) ==  20);
676     assert (round!(roundingMode.DOWN)          ( 19, 1) ==  10);
677     assert (round!(roundingMode.HALF_UP)       ( 15, 1) ==  20);
678     assert (round!(roundingMode.HALF_UP)       (-15, 1) == -10);
679     assert (round!(roundingMode.HALF_DOWN)     ( 15, 1) ==  10);
680     assert (round!(roundingMode.HALF_DOWN)     ( 16, 1) ==  20);
681     assert (round!(roundingMode.HALF_EVEN)     ( 15, 1) ==  20);
682     assert (round!(roundingMode.HALF_EVEN)     ( 25, 1) ==  20);
683     assert (round!(roundingMode.HALF_ODD)      ( 15, 1) ==  10);
684     assert (round!(roundingMode.HALF_ODD)      ( 25, 1) ==  30);
685     assert (round!(roundingMode.HALF_TO_ZERO)  ( 25, 1) ==  20);
686     assert (round!(roundingMode.HALF_TO_ZERO)  ( 26, 1) ==  30);
687     assert (round!(roundingMode.HALF_TO_ZERO)  (-25, 1) == -20);
688     assert (round!(roundingMode.HALF_TO_ZERO)  (-26, 1) == -30);
689     assert (round!(roundingMode.HALF_FROM_ZERO)( 25, 1) ==  30);
690     assert (round!(roundingMode.HALF_FROM_ZERO)( 24, 1) ==  20);
691     assert (round!(roundingMode.HALF_FROM_ZERO)(-25, 1) == -30);
692     assert (round!(roundingMode.HALF_FROM_ZERO)(-24, 1) == -20);
693     // dfmt on
694 }
695 
696 @safe unittest
697 {
698     import std.exception : assertThrown;
699 
700     assert(round!(roundingMode.UNNECESSARY)(10, 1) == 10);
701     assertThrown!ForbiddenRounding(round!(roundingMode.UNNECESSARY)(12, 1) == 10);
702 }
703 
704 /** Round a float to an integer according to rounding mode */
705 // TODO pure nothrow @nogc (Phobos...)
706 real round(real x, roundingMode m) @trusted
707 {
708     FloatingPointControl fpctrl;
709     final switch (m) with (roundingMode)
710     {
711     case UP:
712         return ceil(x);
713     case DOWN:
714         return floor(x);
715     case HALF_UP:
716         return lrint(x);
717     case HALF_DOWN:
718         fpctrl.rounding = FloatingPointControl.roundDown;
719         return lrint(x);
720     case HALF_TO_ZERO:
721         fpctrl.rounding = FloatingPointControl.roundToZero;
722         return lrint(x);
723     case HALF_EVEN:
724     case HALF_ODD:
725     case HALF_FROM_ZERO:
726     case UNNECESSARY:
727         throw new ForbiddenRounding();
728     }
729 }
730 
731 @safe unittest
732 {
733     assert(round(3.5, roundingMode.HALF_DOWN) == 3.0);
734     assert(round(3.8, roundingMode.HALF_TO_ZERO) == 3.0);
735 
736     import std.exception : assertThrown;
737 
738     assertThrown!ForbiddenRounding(round(3.1, roundingMode.UNNECESSARY));
739     assertThrown!ForbiddenRounding(round(3.1, roundingMode.HALF_EVEN));
740     assertThrown!ForbiddenRounding(round(3.1, roundingMode.HALF_ODD));
741     assertThrown!ForbiddenRounding(round(3.1, roundingMode.HALF_FROM_ZERO));
742 }
743 
744 /** Exception is thrown if rounding would have to happen,
745     but roundingMode.UNNECESSARY is specified. */
746 class ForbiddenRounding : Exception
747 {
748     public
749     {
750         @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
751         {
752             super("Rounding is forbidden", file, line, next);
753         }
754     }
755 }
756 
757 /** Overflow can happen with money arithmetic. */
758 class OverflowException : Exception
759 {
760     public
761     {
762         @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
763         {
764             super("Overflow", file, line, next);
765         }
766     }
767 }
768 
769 /** Failure to parse a money amount from string */
770 class ParseError : Exception
771 {
772     public
773     {
774         @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
775         {
776             super("Parse error", file, line, next);
777         }
778     }
779 }
780 
781 unittest
782 {
783     import std.exception : assertThrown;
784     import std.format : format;
785     import std.conv : ConvOverflowException;
786 
787     alias EUR = currency!("EUR");
788     assertThrown!ParseError(EUR("foo"));
789     assertThrown!ParseError(EUR("999999999999999999"));
790     assertThrown!ConvOverflowException(EUR("9999999999999999999999"));
791     EUR x = EUR("123.45");
792     EUR y = EUR("123");
793 
794     assert(format("%f", x) == "123.4500EUR");
795     assert(format("%.1f", x) == "123.5EUR");
796     assert(format("%f", y) == "123.0000EUR");
797 }
798 
799 unittest {
800     // From issue #8  https://github.com/qznc/d-money/issues/8
801     import std.format : format;
802     alias T1 = currency!("T1", 2);
803     auto t1 = T1(-123.45);
804     assert(format("%f", t1) == "-123.45T1");
805     auto t2 = T1("-123.45");
806     assert(format("%f", t2) == "-123.45T1");
807     assert(t1 == t2);
808 }