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 }