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 }