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.algorithm : splitter;
56 import std.math : floor, ceil, lrint, abs, FloatingPointControl;
57 import std.conv : to;
58 import core.checkedint : adds, subs, muls, negs;
59 import std.format : FormatSpec, formattedWrite;
60 import std.range : repeat, chain, empty;
61 import std.traits : hasMember;
62 import std.traits;
63 import std.string;
64 import std.stdio;
65 
66 @nogc pure @safe nothrow private long pow10(int x)
67 {
68     return 10 ^^ x;
69 }
70 
71 pure @safe nothrow private string decimals_format(int x)()
72 {
73     import std.conv : text;
74 
75     return "%0" ~ text(x) ~ "d";
76 }
77 
78 /** Holds an amount of currency **/
79 struct currency(string currency_name, int dec_places = 4, roundingMode rmode = roundingMode.HALF_UP)
80 {
81     alias T = typeof(this);
82     enum __currency = currency_name;
83     enum __dec_places = dec_places;
84     enum __rmode = rmode;
85     enum __factor = 10 ^^ dec_places;
86     long amount;
87 
88     /// Floating point contructor. Uses rmode on x.
89     this(double x)
90     {
91         amount = to!long(round(x * __factor, rmode));
92     }
93 
94     /** String contructor.
95       *
96       * Throws: ParseError or std.conv.ConvOverflowException for invalid inputs
97       */
98     /// Create a new Fixed struct given a string
99     this(Range)(Range s) if (isNarrowString!Range)
100     {
101         s = s.replace(",", "");
102         if (!s.empty)
103         {
104             if (!isNumeric(s))
105             {
106                 throw new ParseError("!isNumeric: " ~ s);
107             }
108             auto spl = s.splitter(".");
109             auto frontItem = spl.front;
110             spl.popFront;
111             typeof(frontItem) decimal;
112             if (!spl.empty)
113                 decimal = spl.front;
114             if (decimal.length > dec_places)
115                 decimal = decimal[0 .. dec_places]; // truncate
116             if (long.max / __factor < frontItem.to!long)
117                 throw new ParseError("Number too large: " ~ s);
118             amount = chain(frontItem, decimal, '0'.repeat(dec_places - decimal.length)).to!long;
119         }
120     }
121 
122     private static T fromLong(long a)
123     {
124         T ret = void;
125         ret.amount = a;
126         return ret;
127     }
128 
129     /// default initialisation value is zero
130     static immutable init = fromLong(0L);
131     /// maximum amount depends on dec_places
132     static immutable max = fromLong(long.max);
133     /// minimum amount depends on dec_places
134     static immutable min = fromLong(long.min);
135 
136     private static immutable dec_mask = __factor;
137 
138     T opCast(T : bool)() const
139     {
140         return amount != 0;
141     }
142 
143     T opCast(T)() const if (isNumeric!T)
144     {
145         return (amount.to!T / __factor).to!T;
146     }
147 
148     unittest
149     {
150         alias EUR = currency!("EUR");
151         auto p = EUR(1.1);
152         assert(cast(int) p == 1);
153         assert(p.to!int == 1);
154         assert(p.to!double == 1.1);
155 
156         auto y = EUR("1,000.000,00");
157         auto z = EUR("1000");
158         assert(y == z);
159     }
160 
161     T opUnary(string s)() if (s == "--" || s == "++")
162     {
163         mixin("amount" ~ s[0] ~ "= __factor;");
164         return this;
165     }
166 
167     T opUnary(string s)() const if (s == "-" || s == "+")
168     {
169         return fromLong(mixin(s ~ "amount"));
170     }
171 
172     unittest
173     {
174         alias EUR = currency!("EUR");
175         auto p = EUR(1.1);
176         assert(to!string(p) == "1.1000EUR");
177 
178         p++;
179         assert(p == EUR("2.1"));
180         ++p;
181         assert(p == EUR("3.1"));
182 
183         assert((-p) == EUR("-3.1"));
184         assert((+p) == EUR("+3.1"));
185 
186         p--;
187         assert(p == EUR("2.1"));
188         --p;
189         assert(p == EUR("1.1"));
190     }
191 
192     /// Can add and subtract money amounts of the same type.
193     T opBinary(string op)(const T rhs) const if (op != "/")
194     {
195         static if (op == "+")
196         {
197             bool overflow;
198             auto ret = fromLong(adds(amount, rhs.amount, overflow));
199             if (overflow)
200                 throw new OverflowException();
201             return ret;
202         }
203         else static if (op == "-")
204         {
205             bool overflow;
206             auto ret = fromLong(subs(amount, rhs.amount, overflow));
207             if (overflow)
208                 throw new OverflowException();
209             return ret;
210         }
211         else
212             static assert(0, "Operator " ~ op ~ " not implemented");
213     }
214 
215     /// Can divide by money amount of the same type
216     /// https://en.wikipedia.org/wiki/Integer#Algebraic_properties
217     /// amount is integer, which is closed under "+|-|*", but not "/"
218     /// so the return type is double to simulate rational
219     double opBinary(string op)(const T rhs) const if (op == "/")
220     {
221         return (amount.to!double) / (rhs.amount.to!double);
222     }
223 
224     /// Can multiply, divide, and modulo with integer values.
225     T opBinary(string op)(const long rhs) const
226     {
227         static if (op == "*")
228         {
229             bool overflow;
230             auto ret = fromLong(muls(amount, rhs, overflow));
231             if (overflow)
232                 throw new OverflowException();
233             return ret;
234         }
235         else static if (op == "/")
236         {
237             return fromLong(amount / rhs);
238         }
239         else static if (op == "%")
240         {
241             const intpart = amount / __factor;
242             return fromLong(intpart % rhs * __factor);
243         }
244         else
245             static assert(0, "Operator " ~ op ~ " not implemented");
246     }
247 
248     /// Can multiply, divide, and modulo floating point numbers.
249     T opBinary(string op)(const real rhs) const
250     {
251         static if (op == "*")
252         {
253             const converted = T(rhs);
254             bool overflow = false;
255             const result = muls(amount, converted.amount, overflow);
256             if (overflow)
257                 throw new OverflowException();
258             return fromLong(result / __factor);
259         }
260         else static if (op == "/")
261         {
262             const converted = T(rhs);
263             bool overflow = false;
264             auto mult = muls(amount, __factor, overflow);
265             if (overflow)
266                 throw new OverflowException();
267             return fromLong(mult / converted.amount);
268         }
269         else static if (op == "%")
270         {
271             const converted = T(rhs);
272             return fromLong(amount % converted.amount);
273         }
274         else
275             static assert(0, "Operator " ~ op ~ " not implemented");
276     }
277 
278     T opBinaryRight(string op)(const real lhs) const
279     {
280         static if (op == "*")
281         {
282             return opBinary!op(lhs); // just swap lhs to rhs
283         }
284         else
285             static assert(0, "Operator " ~ op ~ " not implemented");
286     }
287 
288     unittest
289     {
290         alias EUR = currency!("EUR");
291         auto a = EUR(2);
292         assert((3.0 * a) == (a * 3.0));
293         assert((3 * a) == (a * 3));
294     }
295 
296     /// Can add and subtract money amounts of the same type.
297     void opOpAssign(string op)(const T rhs)
298     {
299         static if (op == "+")
300         {
301             bool overflow;
302             auto ret = adds(amount, rhs.amount, overflow);
303             if (overflow)
304                 throw new OverflowException();
305             amount = ret;
306         }
307         else static if (op == "-")
308         {
309             bool overflow;
310             auto ret = subs(amount, rhs.amount, overflow);
311             if (overflow)
312                 throw new OverflowException();
313             amount = ret;
314         }
315         else
316             static assert(0, "Operator " ~ op ~ " not implemented");
317     }
318 
319     /// Can multiply, divide, and modulo with integer values.
320     void opOpAssign(string op)(const long rhs)
321     {
322         static if (op == "*")
323         {
324             bool overflow;
325             auto ret = muls(amount, rhs, overflow);
326             if (overflow)
327                 throw new OverflowException();
328             amount = ret;
329         }
330         else static if (op == "/")
331         {
332             amount /= rhs;
333         }
334         else static if (op == "%")
335         {
336             const intpart = amount / __factor;
337             amount = intpart % rhs * __factor;
338         }
339         else
340             static assert(0, "Operator " ~ op ~ " not implemented");
341     }
342 
343     /// Can multiply, divide, and modulo floating point numbers.
344     void opOpAssign(string op)(const real rhs)
345     {
346         static if (op == "*")
347         {
348             const converted = T(rhs);
349             bool overflow = false;
350             const result = muls(amount, converted.amount, overflow);
351             if (overflow)
352                 throw new OverflowException();
353             amount = result / __factor;
354         }
355         else static if (op == "/")
356         {
357             const converted = T(rhs);
358             bool overflow = false;
359             auto mult = muls(amount, __factor, overflow);
360             if (overflow)
361                 throw new OverflowException();
362             amount = mult / converted.amount;
363         }
364         else static if (op == "%")
365         {
366             const converted = T(rhs);
367             amount = amount % converted.amount;
368         }
369         else
370             static assert(0, "Operator " ~ op ~ " not implemented");
371     }
372 
373     /// Can check equality with money amounts of the same concurrency and decimal places.
374     bool opEquals(OT)(auto ref const OT other) const 
375             if (isCurrency!OT && other.__currency == currency_name
376                 && other.__dec_places == dec_places)
377     {
378         return other.amount == amount;
379     }
380 
381     bool opEquals(T)(const T other) const if (isNumeric!T)
382     {
383         T other_amount = other * __factor;
384         return other_amount == amount;
385     }
386 
387     /// Can compare with money amounts of the same concurrency.
388     int opCmp(OT)(const OT other) const 
389             if (isCurrency!OT && other.__currency == currency_name)
390     {
391         static if (dec_places == other.__dec_places)
392         {
393             if (other.amount > this.amount)
394                 return -1;
395             if (other.amount < this.amount)
396                 return 1;
397             return 0;
398         }
399         else static if (dec_places < other.__dec_places)
400         {
401             /* D implicitly makes this work for case '>' */
402             auto nthis = this * pow10(other.__dec_places - dec_places);
403             /* overflow check included */
404             if (other.amount > nthis.amount)
405                 return -1;
406             if (other.amount < nthis.amount)
407                 return 1;
408             return 0;
409         }
410         else
411             static assert(0, "opCmp with such 'other' not implemented");
412     }
413 
414     int opCmp(T)(const T other) const if (isNumeric!T)
415     {
416         T other_amount = other * __factor;
417         if (this.amount < other_amount)
418             return -1;
419         else if (this.amount > other_amount)
420             return 1;
421         else
422             return 0;
423     }
424 
425     void toDecimalString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const
426     {
427         formattedWrite(sink, "%d", (amount / dec_mask));
428         sink(".");
429         auto decimals = abs(amount % dec_mask);
430         if (fmt.precision < dec_places)
431         {
432             auto n = dec_places - fmt.precision;
433             decimals = round!(rmode)(decimals, n);
434             decimals = decimals / pow10(n);
435             import std.conv : text;
436 
437             formattedWrite(sink, "%0" ~ text(fmt.precision) ~ "d", decimals);
438         }
439         else
440         {
441             formattedWrite(sink, decimals_format!dec_places(), decimals);
442         }
443     }
444 
445     /// Can convert to string.
446     void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const
447     {
448         switch (fmt.spec)
449         {
450         case 's': /* default e.g. for writeln */
451             goto case;
452         case 'f':
453             toDecimalString(sink, fmt);
454             sink(currency_name);
455             break;
456         case 'F':
457             toDecimalString(sink, fmt);
458             break;
459         case 'd':
460             auto ra = round!rmode(amount, dec_places);
461             formattedWrite(sink, "%d", (ra / dec_mask));
462             sink(currency_name);
463             break;
464         default:
465             throw new Exception("Unknown format specifier: %" ~ fmt.spec);
466         }
467     }
468 }
469 
470 /// Basic usage
471 unittest
472 {
473     alias EUR = currency!("EUR");
474     assert(EUR(100.0001) == EUR(100.00009));
475     assert(EUR(3.10) + EUR(1.40) == EUR(4.50));
476     assert(EUR(3.10) - EUR(1.40) == EUR(1.70));
477     assert(EUR(10.01) * 1.1 == EUR(11.011));
478 
479     import std.format : format;
480 
481     // for writefln("%d", EUR(3.6));
482     assert(format("%d", EUR(3.6)) == "4EUR");
483     assert(format("%d", EUR(3.1)) == "3EUR");
484     // for writefln("%f", EUR(3.141592));
485     assert(format("%f", EUR(3.141592)) == "3.1416EUR");
486     assert(format("%.2f", EUR(3.145)) == "3.15EUR");
487     // From issue #5
488     assert(format("%.4f", EUR(0.01234)) == "0.0123EUR");
489 
490     assert(format("%F", EUR(3.141592)) == "3.1416");
491 }
492 
493 /// Overflow is an error, since silent corruption is worse
494 @safe unittest
495 {
496     import std.exception : assertThrown;
497 
498     alias EUR = currency!("EUR");
499     auto one = EUR(1);
500     assertThrown!OverflowException(EUR.max + one);
501     assertThrown!OverflowException(EUR.min - one);
502 
503     assert(one > 0);
504     assert(one < 2);
505 
506     assert(one == 1);
507     assert(one == 1.0);
508     assert(!(one > 1));
509     assert(!(one < 1));
510 
511     assert(one != 0);
512     assert(one != 0.0);
513     assert(one != 2);
514     assert(one != 2.0);
515 }
516 
517 /// Arithmetic ignores rounding mode
518 @safe unittest
519 {
520     alias EUR = currency!("EUR", 2, roundingMode.UP);
521     auto one = EUR(1);
522     assert(one != one / 3);
523 }
524 
525 /// Generic equality and order
526 @safe unittest
527 {
528     alias USD = currency!("USD", 2);
529     alias EURa = currency!("EUR", 2);
530     alias EURb = currency!("EUR", 4);
531     alias EURc = currency!("EUR", 4, roundingMode.DOWN);
532     // cannot compile with different currencies
533     static assert(!__traits(compiles, EURa(1) == USD(1)));
534     // cannot compile with different dec_places
535     static assert(!__traits(compiles, EURa(1) == EURb(1)));
536     // can check equality if only rounding mode differs
537     assert(EURb(1.01) == EURc(1.01));
538     // cannot compare with different currencies
539     static assert(!__traits(compiles, EURa(1) < USD(1)));
540 }
541 
542 // TODO Using negative dec_places for big numbers?
543 //@nogc @safe unittest
544 //{
545 //    alias USD = currency!("USD", -6);
546 //    assert(USD(1_000_000.00) == USD(1_100_000.));
547 //}
548 
549 enum isCurrency(T) = (hasMember!(T, "amount") && hasMember!(T,
550             "__dec_places") && hasMember!(T, "__rmode"));
551 static assert(isCurrency!(currency!"EUR"));
552 
553 // TODO @safe (due to std.format.format)
554 unittest
555 {
556     alias EUR = currency!("EUR");
557     import std.format : format;
558 
559     assert(format("%s", EUR(3.1)) == "3.1000EUR");
560 
561     import std.exception : assertThrown;
562 
563     assertThrown!Exception(format("%x", EUR(3.1)));
564 }
565 
566 @safe unittest
567 {
568     alias EUR = currency!("EUR");
569     assert(EUR(5) < EUR(6));
570     assert(EUR(6) > EUR(5));
571     assert(EUR(5) >= EUR(5));
572     assert(EUR(5) == EUR(5));
573     assert(EUR(6) != EUR(5));
574 
575     import std.exception : assertThrown;
576 
577     assertThrown!OverflowException(EUR.max * 2);
578     assertThrown!OverflowException(EUR.max * 2.0);
579 }
580 
581 @safe unittest
582 {
583     alias EUR = currency!("EUR");
584     auto x = EUR(42);
585     assert(EUR(84) == x * 2);
586     static assert(!__traits(compiles, x * x));
587     assert(EUR(21) == x / 2);
588     assert(EUR(2) == x % 4);
589 }
590 
591 @safe unittest
592 {
593     alias EURa = currency!("EUR", 2);
594     alias EURb = currency!("EUR", 4);
595     auto x = EURa(1.01);
596     assert(x > EURb(1.0001));
597     assert(x < EURb(1.0101));
598     assert(x <= EURb(1.01));
599 }
600 
601 @safe unittest
602 {
603     alias EUR = currency!("EUR");
604     auto x = EUR(2.22);
605     x += EUR(2.22);
606     assert(x == EUR(4.44));
607     x -= EUR(3.33);
608     assert(x == EUR(1.11));
609     x *= 4;
610     assert(x == EUR(4.44));
611     x /= 2;
612     assert(x == EUR(2.22));
613     x *= 2.0;
614     assert(x == EUR(4.44));
615     x /= 2.0;
616     assert(x == EUR(2.22));
617     x %= 3.0;
618     assert(x == EUR(2.22));
619     x %= 3;
620     assert(x == EUR(2));
621 }
622 
623 @safe unittest
624 {
625     import std.exception : assertThrown;
626 
627     alias EUR = currency!("EUR");
628     EUR x = EUR.max;
629     EUR y = EUR.min;
630     assertThrown!OverflowException(x += EUR(1));
631     assert(x == EUR.max);
632     assertThrown!OverflowException(y -= EUR(1));
633     assert(y == EUR.min);
634     assertThrown!OverflowException(x *= 2);
635     assert(x == EUR.max);
636     assertThrown!OverflowException(x *= 2.0);
637     assert(x == EUR.max);
638     assertThrown!OverflowException(y /= 10.0);
639     assert(y == EUR.min);
640 }
641 
642 @safe unittest
643 {
644     alias EUR = currency!("EUR");
645     auto x = EUR(35.0);
646     assert(x / EUR(7.0) == 5.0);
647     assert(x / EUR(-5.0) == -7.0);
648     // default is four significant digits
649     assert(x / EUR(1.2) - 29.1667 < 0.000049);
650 }
651 
652 /** Specifies rounding behavior **/
653 enum roundingMode
654 {
655     // dfmt off
656     /** Round upwards, e.g. 3.1 up to 4. */
657     UP,
658     /** Round downwards, e.g. 3.9 down to 3. */
659     DOWN,
660     /** Round to nearest number, half way between round up, e.g. 3.5 to 4. */
661     HALF_UP,
662     /** Round to nearest number, half way between round dow, e.g. 3.5 to 3.  */
663     HALF_DOWN,
664     /** Round to nearest number, half way between round to even number, e.g. 3.5 to 4. */
665     HALF_EVEN,
666     /** Round to nearest number, half way between round to odd number, e.g. 3.5 to 3. */
667     HALF_ODD,
668     /** Round to nearest number, half way between round towards zero, e.g. -3.5 to -3.  */
669     HALF_TO_ZERO,
670     /** Round to nearest number, half way between round away from zero, e.g. -3.5 to -4.  */
671     HALF_FROM_ZERO,
672     /** Throw exception if rounding would be necessary */
673     UNNECESSARY
674       // dfmt on
675 }
676 
677 /** Round an integer to a certain decimal place according to rounding mode */
678 long round(roundingMode m)(long x, int dec_place)
679 out (result)
680 {
681     assert((result % pow10(dec_place)) == 0);
682 }
683 do
684 {
685     const zeros = pow10(dec_place);
686     /* short cut, also removes edge cases */
687     if ((x % zeros) == 0)
688         return x;
689 
690     const half = zeros / 2;
691     with (roundingMode)
692     {
693         static if (m == UP)
694         {
695             return ((x / zeros) + 1) * zeros;
696         }
697         else static if (m == DOWN)
698         {
699             return x / zeros * zeros;
700         }
701         else static if (m == HALF_UP)
702         {
703             if ((x % zeros) >= half)
704                 return ((x / zeros) + 1) * zeros;
705             else
706                 return x / zeros * zeros;
707         }
708         else static if (m == HALF_DOWN)
709         {
710             if ((x % zeros) > half)
711                 return ((x / zeros) + 1) * zeros;
712             else
713                 return x / zeros * zeros;
714         }
715         else static if (m == HALF_EVEN)
716         {
717             const down = x / zeros;
718             if (down % 2 == 0)
719                 return down * zeros;
720             else
721                 return (down + 1) * zeros;
722         }
723         else static if (m == HALF_ODD)
724         {
725             const down = x / zeros;
726             if (down % 2 == 0)
727                 return (down + 1) * zeros;
728             else
729                 return down * zeros;
730         }
731         else static if (m == HALF_TO_ZERO)
732         {
733             const down = x / zeros;
734             if (down < 0)
735             {
736                 if (abs(x % zeros) <= half)
737                 {
738                     return (down) * zeros;
739                 }
740                 else
741                 {
742                     return (down - 1) * zeros;
743                 }
744             }
745             else
746             {
747                 if ((x % zeros) > half)
748                 {
749                     return (down + 1) * zeros;
750                 }
751                 else
752                 {
753                     return (down) * zeros;
754                 }
755             }
756         }
757         else static if (m == HALF_FROM_ZERO)
758         {
759             const down = x / zeros;
760             if (down < 0)
761             {
762                 if (abs(x % zeros) < half)
763                 {
764                     return (down) * zeros;
765                 }
766                 else
767                 {
768                     return (down - 1) * zeros;
769                 }
770             }
771             else
772             {
773                 if (x % zeros >= half)
774                 {
775                     return (down + 1) * zeros;
776                 }
777                 else
778                 {
779                     return (down) * zeros;
780                 }
781             }
782         }
783         else static if (m == UNNECESSARY)
784         {
785             throw new ForbiddenRounding();
786         }
787     }
788 }
789 
790 // dfmt off
791 ///
792 @nogc @safe unittest
793 {
794     assert (round!(roundingMode.DOWN)     (1009, 1) == 1000);
795     assert (round!(roundingMode.UP)       (1001, 1) == 1010);
796     assert (round!(roundingMode.HALF_UP)  (1005, 1) == 1010);
797     assert (round!(roundingMode.HALF_DOWN)(1005, 1) == 1000);
798 }
799 // dfmt on
800 
801 @safe pure @nogc nothrow unittest
802 {
803     // dfmt off
804     assert (round!(roundingMode.HALF_UP)       ( 10, 1) ==  10);
805     assert (round!(roundingMode.UP)            ( 11, 1) ==  20);
806     assert (round!(roundingMode.DOWN)          ( 19, 1) ==  10);
807     assert (round!(roundingMode.HALF_UP)       ( 15, 1) ==  20);
808     assert (round!(roundingMode.HALF_UP)       (-15, 1) == -10);
809     assert (round!(roundingMode.HALF_DOWN)     ( 15, 1) ==  10);
810     assert (round!(roundingMode.HALF_DOWN)     ( 16, 1) ==  20);
811     assert (round!(roundingMode.HALF_EVEN)     ( 15, 1) ==  20);
812     assert (round!(roundingMode.HALF_EVEN)     ( 25, 1) ==  20);
813     assert (round!(roundingMode.HALF_ODD)      ( 15, 1) ==  10);
814     assert (round!(roundingMode.HALF_ODD)      ( 25, 1) ==  30);
815     assert (round!(roundingMode.HALF_TO_ZERO)  ( 25, 1) ==  20);
816     assert (round!(roundingMode.HALF_TO_ZERO)  ( 26, 1) ==  30);
817     assert (round!(roundingMode.HALF_TO_ZERO)  (-25, 1) == -20);
818     assert (round!(roundingMode.HALF_TO_ZERO)  (-26, 1) == -30);
819     assert (round!(roundingMode.HALF_FROM_ZERO)( 25, 1) ==  30);
820     assert (round!(roundingMode.HALF_FROM_ZERO)( 24, 1) ==  20);
821     assert (round!(roundingMode.HALF_FROM_ZERO)(-25, 1) == -30);
822     assert (round!(roundingMode.HALF_FROM_ZERO)(-24, 1) == -20);
823     // dfmt on
824 }
825 
826 @safe unittest
827 {
828     import std.exception : assertThrown;
829 
830     assert(round!(roundingMode.UNNECESSARY)(10, 1) == 10);
831     assertThrown!ForbiddenRounding(round!(roundingMode.UNNECESSARY)(12, 1) == 10);
832 }
833 
834 /** Round a float to an integer according to rounding mode */
835 // TODO pure nothrow @nogc (Phobos...)
836 real round(real x, roundingMode m) @trusted
837 {
838     FloatingPointControl fpctrl;
839     final switch (m) with (roundingMode)
840     {
841     case UP:
842         return ceil(x);
843     case DOWN:
844         return floor(x);
845     case HALF_UP:
846         return lrint(x);
847     case HALF_DOWN:
848         fpctrl.rounding = FloatingPointControl.roundDown;
849         return lrint(x);
850     case HALF_TO_ZERO:
851         fpctrl.rounding = FloatingPointControl.roundToZero;
852         return lrint(x);
853     case HALF_EVEN:
854     case HALF_ODD:
855     case HALF_FROM_ZERO:
856     case UNNECESSARY:
857         throw new ForbiddenRounding();
858     }
859 }
860 
861 @safe unittest
862 {
863     assert(round(3.5, roundingMode.HALF_DOWN) == 3.0);
864     assert(round(3.8, roundingMode.HALF_TO_ZERO) == 3.0);
865 
866     import std.exception : assertThrown;
867 
868     assertThrown!ForbiddenRounding(round(3.1, roundingMode.UNNECESSARY));
869     assertThrown!ForbiddenRounding(round(3.1, roundingMode.HALF_EVEN));
870     assertThrown!ForbiddenRounding(round(3.1, roundingMode.HALF_ODD));
871     assertThrown!ForbiddenRounding(round(3.1, roundingMode.HALF_FROM_ZERO));
872 }
873 
874 /** Exception is thrown if rounding would have to happen,
875     but roundingMode.UNNECESSARY is specified. */
876 class ForbiddenRounding : Exception
877 {
878     public
879     {
880         @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
881         {
882             super("Rounding is forbidden", file, line, next);
883         }
884     }
885 }
886 
887 /** Overflow can happen with money arithmetic. */
888 class OverflowException : Exception
889 {
890     public
891     {
892         @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
893         {
894             super("Overflow", file, line, next);
895         }
896     }
897 }
898 
899 /** Failure to parse a money amount from string */
900 class ParseError : Exception
901 {
902     public
903     {
904         @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
905         {
906             super("Parse error", file, line, next);
907         }
908     }
909 }
910 
911 unittest
912 {
913     import std.exception : assertThrown;
914     import std.format : format;
915     import std.conv : ConvOverflowException;
916 
917     alias EUR = currency!("EUR");
918     assertThrown!ParseError(EUR("foo"));
919     assertThrown!ParseError(EUR("999999999999999999"));
920     assertThrown!ConvOverflowException(EUR("9999999999999999999999"));
921     EUR x = EUR("123.45");
922     EUR y = EUR("123");
923 
924     assert(format("%f", x) == "123.4500EUR");
925     assert(format("%.1f", x) == "123.5EUR");
926     assert(format("%f", y) == "123.0000EUR");
927 }
928 
929 unittest
930 {
931     // From issue #8  https://github.com/qznc/d-money/issues/8
932     import std.format : format;
933 
934     alias T1 = currency!("T1", 2);
935     auto t1 = T1(-123.45);
936     assert(format("%f", t1) == "-123.45T1");
937     auto t2 = T1("-123.45");
938     assert(format("%f", t2) == "-123.45T1");
939     assert(t1 == t2);
940 }
941 
942 unittest
943 {
944     // From https://github.com/qznc/d-money/issues/11
945     alias EUR = currency!("EUR");
946     EUR x = EUR(1.23456); // rounded
947     EUR y = EUR("1.23456"); // truncated
948     EUR z = EUR("1.23456789"); // truncated
949     assert(8 == y.sizeof);
950     assert(x >= y);
951     assert(z == y);
952 }