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 }