1 /* 2 Adapted from the specification of the General Decimal Arithmetic. 3 4 This implementation is written for the D Programming Language 5 by Jack Stouffer and is licensed under the Boost Software License 1.0. 6 */ 7 module stdxdecimal; 8 9 import std.stdio; 10 import std.range.primitives; 11 import std.traits; 12 13 /** 14 * Behavior is defined by `Hook`. Number of significant digits is limited by 15 * `Hook.precision`. 16 * 17 * Spec: http://speleotrove.com/decimal/decarith.html 18 */ 19 struct Decimal(Hook = Abort) 20 { 21 import std.experimental.allocator.common : stateSize; 22 23 static assert( 24 hasMember!(Hook, "precision") && is(typeof(Hook.precision) : uint), 25 "The Hook must have a defined precision that's convertible to uint" 26 ); 27 static assert( 28 isEnum!(Hook.precision), 29 "Hook.precision must be readable at compile-time" 30 ); 31 static assert( 32 hasMember!(Hook, "roundingMode") && is(typeof(Hook.roundingMode) == Rounding), 33 "The Hook must have a defined Rounding" 34 ); 35 static assert( 36 isEnum!(Hook.roundingMode), 37 "Hook.roundingMode must be readable at compile-time" 38 ); 39 static assert( 40 hook.precision > 1, 41 "Hook.precision is too small (must be at least 2)" 42 ); 43 44 package: 45 // 1 indicates that the number is negative or is the negative zero 46 // and 0 indicates that the number is zero or positive. 47 bool sign; 48 // quiet NaN 49 bool qNaN; 50 // signaling NaN 51 bool sNaN; 52 // Infinite 53 bool inf; 54 55 // actual value of decimal given as (–1)^^sign × coefficient × 10^^exponent 56 static if (useBigInt) 57 { 58 import std.BigInt : BigInt; 59 BigInt coefficient; 60 } 61 else 62 { 63 ulong coefficient; 64 } 65 long exponent; 66 67 enum hasClampedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onClamped(d); }); 68 enum hasRoundedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onRounded(d); }); 69 enum hasInexactMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInexact(d); }); 70 enum hasDivisionByZeroMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onDivisionByZero(d); }); 71 enum hasInvalidOperationMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInvalidOperation(d); }); 72 enum hasOverflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onOverflow(d); }); 73 enum hasSubnormalMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onSubnormal(d); }); 74 enum hasUnderflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onUnderflow(d); }); 75 76 // The cut off point is set at 9 because 999_999_999 ^^ 2 77 // can still fit in a ulong 78 enum useBigInt = Hook.precision > 9; 79 80 /* 81 rounds the coefficient via `Hook`s rounding mode. 82 83 Rounded is always set if the coefficient is changed 84 85 Inexact is set if the rounded digits were non-zero 86 */ 87 auto round() 88 { 89 auto digits = numberOfDigits(coefficient); 90 91 if (digits <= hook.precision) 92 return; 93 94 typeof(coefficient) lastDigit; 95 96 static if (hook.roundingMode == Rounding.Down) 97 { 98 while (digits > hook.precision) 99 { 100 if (!inexact) 101 lastDigit = coefficient % 10; 102 coefficient /= 10; 103 --digits; 104 ++exponent; 105 106 if (!inexact && lastDigit != 0) 107 inexact = true; 108 } 109 } 110 else static if (hook.roundingMode == Rounding.Up) 111 { 112 while (digits > hook.precision) 113 { 114 if (!inexact) 115 lastDigit = coefficient % 10; 116 coefficient /= 10; 117 --digits; 118 ++exponent; 119 120 if (!inexact && lastDigit != 0) 121 inexact = true; 122 } 123 124 // If all of the discarded digits are zero the result is unchanged 125 if (inexact) 126 ++coefficient; 127 } 128 else static if (hook.roundingMode == Rounding.HalfUp) 129 { 130 while (digits > hook.precision + 1) 131 { 132 if (!inexact) 133 lastDigit = coefficient % 10; 134 135 coefficient /= 10; 136 --digits; 137 ++exponent; 138 139 if (!inexact && lastDigit != 0) 140 inexact = true; 141 } 142 143 lastDigit = coefficient % 10; 144 if (lastDigit != 0) 145 inexact = true; 146 coefficient /= 10; 147 ++exponent; 148 149 if (lastDigit >= 5) 150 ++coefficient; 151 } 152 else 153 { 154 static assert(0, "Not implemented"); 155 } 156 157 rounded = true; 158 159 static if (hasInexactMethod) 160 if (inexact) 161 hook.onInexact(this); 162 163 static if (hasRoundedMethod) 164 hook.onRounded(this); 165 166 return; 167 } 168 169 public: 170 /** 171 * `hook` is a member variable if it has state, or an alias for `Hook` 172 * otherwise. 173 */ 174 static if (stateSize!Hook > 0) 175 Hook hook; 176 else 177 alias hook = Hook; 178 179 /// Public flags 180 bool clamped; 181 /// ditto 182 bool divisionByZero; 183 /// ditto 184 bool inexact; 185 /// ditto 186 bool invalidOperation; 187 /// ditto 188 bool overflow; 189 /// ditto 190 bool rounded; 191 /// ditto 192 bool subnormal; 193 /// ditto 194 bool underflow; 195 196 /** 197 * Note: Float construction less accurate that string, Use 198 * string construction if possible 199 */ 200 this(T)(const T num) pure if (isNumeric!T) 201 { 202 // the behavior of conversion from built-in number types 203 // isn't covered by the spec, so we can do whatever we 204 // want here 205 206 static if (isIntegral!T) 207 { 208 import std.math : abs; 209 210 coefficient = abs(num); 211 sign = num >= 0 ? 0 : 1; 212 } 213 else 214 { 215 import std.math : abs, isInfinity, isNaN; 216 217 Unqual!T val = num; 218 219 if (isInfinity(val)) 220 { 221 inf = true; 222 sign = val < 0 ? 0 : 1; 223 return; 224 } 225 226 if (isNaN(val)) 227 { 228 qNaN = true; 229 sign = val == T.nan ? 0 : 1; 230 return; 231 } 232 233 sign = val >= 0 ? 0 : 1; 234 val = abs(val); 235 236 // while the number still has a fractional part, multiply by 10, 237 // counting each time until no fractional part 238 Unqual!T fraction = val - (cast(long) val); 239 while (fraction > 0) 240 { 241 --exponent; 242 val *= 10; 243 fraction = val - (cast(long) val); 244 } 245 246 coefficient = cast(size_t) val; 247 } 248 249 round(); 250 } 251 252 /** 253 * implements spec to-number 254 */ 255 this(S)(S str) if (isForwardRange!S && isSomeChar!(ElementEncodingType!S) && !isInfinite!S) 256 { 257 import std.algorithm.comparison : among, equal; 258 import std.algorithm.iteration : filter, map; 259 import std.algorithm.searching : startsWith; 260 import std.ascii : isDigit, toLower; 261 import std.utf : byChar, byCodeUnit; 262 263 static bool asciiCmp(S1)(S1 a, string b) 264 { 265 return a.map!toLower.equal(b.byChar.map!toLower); 266 } 267 268 // valid characters in non-special number strings 269 static bool charCheck(dchar a) 270 { 271 return isDigit(a) || a == 'E' || a == 'e' || a == '-' || a == '+' || a == '.'; 272 } 273 274 static if (isSomeString!S) 275 auto codeUnits = str.byCodeUnit; 276 else 277 alias codeUnits = str; 278 279 if (codeUnits.empty) 280 { 281 qNaN = true; 282 return; 283 } 284 285 // all variables are declared here to make gotos compile 286 immutable frontResult = codeUnits.front; 287 bool sawDecimal = false; 288 bool sawExponent = false; 289 bool sawExponentSign = false; 290 byte exponentSign; 291 long sciExponent = 0; 292 293 if (frontResult == '+') 294 { 295 codeUnits.popFront; 296 } 297 else if (frontResult == '-') 298 { 299 sign = 1; 300 codeUnits.popFront; 301 } 302 303 if (codeUnits.empty) 304 { 305 sign = 0; 306 goto Lerr; 307 } 308 309 if (codeUnits.among!((a, b) => asciiCmp(a.save, b)) 310 ("inf", "infinity")) 311 { 312 inf = true; 313 return; 314 } 315 316 // having numbers after nan is valid in the spec 317 if (codeUnits.save.map!toLower.startsWith("qnan".byChar) || 318 codeUnits.save.map!toLower.startsWith("nan".byChar)) 319 { 320 qNaN = true; 321 return; 322 } 323 324 if (codeUnits.save.map!toLower.startsWith("snan".byChar)) 325 { 326 sNaN = true; 327 return; 328 } 329 330 for (; !codeUnits.empty; codeUnits.popFront) 331 { 332 auto digit = codeUnits.front; 333 334 if (!charCheck(digit)) 335 goto Lerr; 336 337 if (isDigit(digit)) 338 { 339 if (!sawExponent) 340 { 341 coefficient *= 10; 342 coefficient += cast(uint) (digit - '0'); 343 } 344 345 if (sawDecimal && !sawExponent) 346 exponent--; 347 348 if (sawExponent) 349 { 350 while (!codeUnits.empty) 351 { 352 if (!isDigit(codeUnits.front)) 353 goto Lerr; 354 355 sciExponent += cast(uint) (codeUnits.front - '0'); 356 if (!codeUnits.empty) 357 { 358 codeUnits.popFront; 359 if (!codeUnits.empty) 360 sciExponent *= 10; 361 } 362 } 363 364 if (sawExponentSign && exponentSign == -1) 365 sciExponent *= -1; 366 367 exponent += sciExponent; 368 369 if (codeUnits.empty) 370 { 371 round(); 372 return; 373 } 374 } 375 } 376 377 if (digit == '+' || digit == '-') 378 { 379 // already have exponent sign, bad input so cancel out 380 if (sawExponentSign) 381 goto Lerr; 382 383 if (sawExponent) 384 { 385 if (digit == '-') 386 exponentSign = -1; 387 sawExponentSign = true; 388 } 389 else 390 { // no exponent yet, bad input so cancel out 391 goto Lerr; 392 } 393 } 394 395 if (digit == '.') 396 { 397 // already have decimal, bad input so cancel out 398 if (sawDecimal) 399 goto Lerr; 400 401 sawDecimal = true; 402 } 403 404 if (digit.toLower == 'e') 405 { 406 // already have exponent, bad input so cancel out 407 if (sawExponent) 408 goto Lerr; 409 410 sawExponent = true; 411 } 412 } 413 414 round(); 415 return; 416 417 Lerr: 418 qNaN = true; 419 coefficient = 0; 420 exponent = 0; 421 422 invalidOperation = true; 423 static if (hasInvalidOperationMethod) 424 hook.onInvalidOperation(this); 425 426 return; 427 } 428 429 /** 430 * The result has the hook of the left hand side. Invalid operations 431 * call `onInvalidOperation` on the `hook` on the result and set the 432 * result's flag to `true`. Does not effect the left hand side of the 433 * operation. 434 * 435 * When the right hand side is a built-in numeric type, the default 436 * hook `Abort` is used for its decimal representation. 437 */ 438 auto opBinary(string op, T)(T rhs) const if (op == "+" || op == "-") 439 { 440 static if (isNumeric!T) 441 { 442 auto temp = decimal(rhs); 443 return mixin("this " ~ op ~ " temp"); 444 } 445 else static if (op == "+" || op == "-") 446 { 447 import std.algorithm.comparison : min; 448 import std.math : abs; 449 450 static if (op == "-") 451 rhs.sign = rhs.sign == 0 ? 1 : 0; 452 453 Decimal!(hook) res; 454 455 if (sNaN || rhs.sNaN) 456 { 457 if (sign == 0) 458 { 459 res.sNaN = true; 460 } 461 else 462 { 463 res.sNaN = true; 464 res.sign = 1; 465 } 466 467 res.invalidOperation = true; 468 static if (hasInvalidOperationMethod) 469 res.hook.onInvalidOperation(this); 470 return res; 471 } 472 473 if (qNaN || rhs.qNaN) 474 { 475 if (sign == 1) 476 res.sign = 1; 477 478 res.qNaN = true; 479 return res; 480 } 481 482 if (inf && rhs.inf) 483 { 484 if (sign == 1 && rhs.sign == 1) 485 { 486 res.sign = 1; 487 res.inf = true; 488 return res; 489 } 490 491 if (sign == 0 && rhs.sign == 0) 492 { 493 res.inf = true; 494 return res; 495 } 496 497 // -Inf + Inf makes no sense 498 res.qNaN = true; 499 res.invalidOperation = true; 500 return res; 501 } 502 503 if (inf) 504 { 505 res.inf = true; 506 res.sign = sign; 507 return res; 508 } 509 510 if (rhs.inf) 511 { 512 res.inf = true; 513 res.sign = rhs.sign; 514 return res; 515 } 516 517 res = Decimal!(hook)(0); 518 Unqual!(typeof(coefficient)) alignedCoefficient = coefficient; 519 Unqual!(typeof(rhs.coefficient)) rhsAlignedCoefficient = rhs.coefficient; 520 521 if (exponent != rhs.exponent) 522 { 523 long diff; 524 bool overflow; 525 526 if (exponent > rhs.exponent) 527 { 528 diff = abs(exponent - rhs.exponent); 529 530 static if (useBigInt) 531 { 532 alignedCoefficient *= 10 ^^ diff; 533 } 534 else 535 { 536 import core.checkedint : mulu; 537 alignedCoefficient = mulu(alignedCoefficient, 10 ^^ diff, overflow); 538 // the Overflow condition is only raised if exponents are incorrect, 539 // has nothing to do with coefficients, so abort 540 if (overflow) 541 assert(0, "Arithmetic operation failed due to coefficient overflow"); 542 } 543 } 544 else 545 { 546 diff = abs(rhs.exponent - exponent); 547 548 static if (useBigInt) 549 { 550 rhsAlignedCoefficient *= 10 ^^ diff; 551 } 552 else 553 { 554 import core.checkedint : mulu; 555 rhsAlignedCoefficient = mulu(rhsAlignedCoefficient, 10 ^^ diff, overflow); 556 if (overflow) 557 assert(0, "Arithmetic operation failed due to coefficient overflow"); 558 } 559 } 560 } 561 562 // If the signs of the operands differ then the smaller aligned coefficient 563 // is subtracted from the larger; otherwise they are added. 564 if (sign == rhs.sign) 565 { 566 if (alignedCoefficient >= rhsAlignedCoefficient) 567 res.coefficient = alignedCoefficient + rhsAlignedCoefficient; 568 else 569 res.coefficient = rhsAlignedCoefficient + alignedCoefficient; 570 } 571 else 572 { 573 if (alignedCoefficient >= rhsAlignedCoefficient) 574 res.coefficient = alignedCoefficient - rhsAlignedCoefficient; 575 else 576 res.coefficient = rhsAlignedCoefficient - alignedCoefficient; 577 } 578 579 res.exponent = min(exponent, rhs.exponent); 580 581 if (res.coefficient != 0) 582 { 583 // the sign of the result is the sign of the operand having 584 // the larger absolute value. 585 if (alignedCoefficient >= rhsAlignedCoefficient) 586 res.sign = sign; 587 else 588 res.sign = rhs.sign; 589 } 590 else 591 { 592 if (sign == 1 && rhs.sign == 1) 593 res.sign = 1; 594 595 static if (hook.roundingMode == Rounding.Floor) 596 if (sign != rhs.sign) 597 res.sign = 1; 598 } 599 600 res.round(); 601 return res; 602 } 603 else 604 { 605 static assert(0, "Not implemented yet"); 606 } 607 } 608 609 /** 610 * The spec says that comparing `NAN`s should yield `NAN`. 611 * Unfortunately this isn't possible in D, as the return value of `opCmp` must be 612 * [`-1`, `1`]. 613 * 614 * Further, in D, the NaN values of floating point types always return `false` in 615 * any comparison. But, this makes sorting an array with NaN values impossible. 616 * 617 * So, `-INF` is less than all numbers, `-NAN` is greater than `-INF` but 618 * less than all other numbers, `NAN` is greater than `-NAN` but less than all other 619 * numbers and inf is greater than all numbers. `-NAN` and `NAN` are equal to 620 * themselves. 621 * 622 * Signaling NAN is an invalid operation, and will trigger the appropriate hook 623 * method and always yield `-1`. 624 */ 625 int opCmp(T)(const T d) // For some reason isInstanceOf refuses to work here 626 { 627 static if (!isNumeric!T) 628 { 629 if (sNaN || d.sNaN) 630 { 631 invalidOperation = true; 632 static if (hasInvalidOperationMethod) 633 hook.onInvalidOperation(this); 634 return -1; 635 } 636 637 if (inf && sign == 1 && (inf != d.inf || sign != d.sign)) 638 return -1; 639 if (inf && sign == 0 && (inf != d.inf || sign != d.sign)) 640 return 1; 641 if (inf && d.inf && sign == d.sign) 642 return 0; 643 644 if (qNaN && d.qNaN) 645 { 646 if (sign == d.sign) 647 return 0; 648 if (sign == 1) 649 return -1; 650 651 return 1; 652 } 653 654 if (qNaN && !d.qNaN) 655 return -1; 656 if (!qNaN && d.qNaN) 657 return 1; 658 659 Decimal!(Hook) lhs; 660 Decimal!(d.hook) rhs; 661 662 // If the signs of the operands differ, a value representing each 663 // operand (’-1’ if the operand is less than zero, ’0’ if the 664 // operand is zero or negative zero, or ’1’ if the operand is 665 // greater than zero) is used in place of that operand for the 666 // comparison instead of the actual operand. 667 if (sign != d.sign) 668 { 669 if (sign == 0) 670 { 671 if (coefficient > 0) 672 lhs.coefficient = 1; 673 } 674 else 675 { 676 if (coefficient > 0) 677 { 678 lhs.sign = 1; 679 lhs.coefficient = 1; 680 } 681 } 682 683 if (d.sign == 0) 684 { 685 if (d.coefficient > 0) 686 rhs.coefficient = 1; 687 } 688 else 689 { 690 if (d.coefficient > 0) 691 { 692 rhs.sign = 1; 693 rhs.coefficient = 1; 694 } 695 } 696 } 697 else 698 { 699 lhs.sign = sign; 700 lhs.coefficient = coefficient; 701 lhs.exponent = exponent; 702 rhs.sign = d.sign; 703 rhs.coefficient = d.coefficient; 704 rhs.exponent = d.exponent; 705 } 706 707 auto res = lhs - rhs; 708 709 if (res.sign == 0) 710 { 711 if (res.coefficient == 0) 712 return 0; 713 else 714 return 1; 715 } 716 717 return -1; 718 } 719 else 720 { 721 return this.opCmp(d.decimal); 722 } 723 } 724 725 /// 726 bool opEquals(T)(const T d) 727 { 728 return this.opCmp(d) == 0; 729 } 730 731 /// 732 alias toString = toDecimalString; 733 734 /** 735 * Decimal strings 736 * 737 * Special Values: 738 * Quiet Not A Number = `NaN` 739 * Signal Not A Number = `sNaN` 740 * Infinite = `Infinity` 741 * 742 * If negative, then all of above have `-` pre-pended 743 */ 744 auto toDecimalString() const 745 { 746 import std.array : appender; 747 import std.math : abs; 748 749 auto app = appender!string(); 750 if (exponent > 10 || exponent < -10) 751 app.reserve(abs(exponent) + hook.precision); 752 toDecimalString(app); 753 return app.data; 754 } 755 756 /// ditto 757 void toDecimalString(Writer)(auto ref Writer w) const if (isOutputRange!(Writer, char)) 758 { 759 import std.math : pow; 760 import std.range : repeat; 761 762 if (sign == 1) 763 w.put('-'); 764 765 if (inf) 766 { 767 w.put("Infinity"); 768 return; 769 } 770 771 if (qNaN) 772 { 773 w.put("NaN"); 774 return; 775 } 776 777 if (sNaN) 778 { 779 w.put("sNaN"); 780 return; 781 } 782 783 static if (useBigInt) 784 { 785 import std.bigint : toDecimalString; 786 auto temp = coefficient.toDecimalString; 787 } 788 else 789 { 790 import std.conv : toChars; 791 auto temp = coefficient.toChars; 792 } 793 794 auto decimalPlace = exponent * -1; 795 796 if (decimalPlace > 0) 797 { 798 if (temp.length - decimalPlace == 0) 799 { 800 w.put("0."); 801 w.put(temp); 802 return; 803 } 804 805 if ((cast(long) temp.length) - decimalPlace > 0) 806 { 807 w.put(temp[0 .. $ - decimalPlace]); 808 w.put('.'); 809 w.put(temp[$ - decimalPlace .. $]); 810 return; 811 } 812 813 if ((cast(long) temp.length) - decimalPlace < 0) 814 { 815 w.put("0."); 816 w.put('0'.repeat(decimalPlace - temp.length)); 817 w.put(temp); 818 return; 819 } 820 } 821 822 if (decimalPlace < 0) 823 { 824 w.put(temp); 825 w.put('0'.repeat(exponent)); 826 return; 827 } 828 829 w.put(temp); 830 } 831 } 832 833 // string construction 834 @safe pure nothrow 835 unittest 836 { 837 static struct Test 838 { 839 string val; 840 ubyte sign; 841 int coefficient; 842 long exponent; 843 } 844 845 static struct SpecialTest 846 { 847 string val; 848 ubyte sign; 849 bool qNaN; 850 bool sNaN; 851 bool inf; 852 bool invalid; 853 } 854 855 auto nonspecialTestValues = [ 856 Test("0", 0, 0, 0), 857 Test("+0", 0, 0, 0), 858 Test("-0", 1, 0, 0), 859 Test("1.0", 0, 10, -1), 860 Test("0E+7", 0, 0, 7), 861 Test("-0E-7", 1, 0, -7), 862 Test("1.23E3", 0, 123, 1), 863 Test("0001.0000", 0, 10000, -4), 864 Test("-10.0004", 1, 100004, -4), 865 Test("+15", 0, 15, 0), 866 Test("-15", 1, 15, 0), 867 Test("1234.5E-4", 0, 12345, -5), 868 Test("30.5E10", 0, 305, 9) 869 ]; 870 871 auto specialTestValues = [ 872 SpecialTest("NaN", 0, true, false, false), 873 SpecialTest("+nan", 0, true, false, false), 874 SpecialTest("-nan", 1, true, false, false), 875 SpecialTest("-NAN", 1, true, false, false), 876 SpecialTest("Infinite", 0, true, false, false, true), 877 SpecialTest("infinity", 0, false, false, true), 878 SpecialTest("-INFINITY", 1, false, false, true), 879 SpecialTest("inf", 0, false, false, true), 880 SpecialTest("-inf", 1, false, false, true), 881 SpecialTest("snan", 0, false, true, false), 882 SpecialTest("-snan", 1, false, true, false), 883 SpecialTest("Jack", 0, true, false, false, true), 884 SpecialTest("+", 0, true, false, false, true), 885 SpecialTest("-", 0, true, false, false, true), 886 SpecialTest("nan0123", 0, true, false, false), 887 SpecialTest("-nan0123", 1, true, false, false), 888 SpecialTest("snan0123", 0, false, true, false), 889 SpecialTest("12+3", 0, true, false, false, true), 890 SpecialTest("1.2.3", 0, true, false, false, true), 891 SpecialTest("123.0E+7E+7", 0, true, false, false, true), 892 ]; 893 894 foreach (el; nonspecialTestValues) 895 { 896 auto d = Decimal!()(el.val); 897 assert(d.coefficient == el.coefficient); 898 assert(d.sign == el.sign); 899 assert(d.exponent == el.exponent); 900 } 901 902 foreach (el; specialTestValues) 903 { 904 auto d = Decimal!(NoOp)(el.val); 905 assert(d.qNaN == el.qNaN); 906 assert(d.sNaN == el.sNaN); 907 assert(d.inf == el.inf); 908 assert(d.invalidOperation == el.invalid); 909 } 910 } 911 912 // range construction 913 @system pure 914 unittest 915 { 916 import std.internal.test.dummyrange; 917 auto r1 = new ReferenceForwardRange!dchar("123.456"); 918 auto d1 = Decimal!()(r1); 919 assert(d1.coefficient == 123456); 920 assert(d1.sign == 0); 921 assert(d1.exponent == -3); 922 923 auto r2 = new ReferenceForwardRange!dchar("-0.00004"); 924 auto d2 = Decimal!()(r2); 925 assert(d2.coefficient == 4); 926 assert(d2.sign == 1); 927 assert(d2.exponent == -5); 928 } 929 930 // int construction 931 @safe pure nothrow 932 unittest 933 { 934 static struct Test 935 { 936 long val; 937 ubyte sign; 938 long coefficient; 939 } 940 941 auto testValues = [ 942 Test(10, 0, 10), 943 Test(-10, 1, 10), 944 Test(-1000000, 1, 1000000), 945 Test(-147_483_648, 1, 147_483_648), 946 ]; 947 948 foreach (el; testValues) 949 { 950 auto d = Decimal!()(el.val); 951 assert(d.coefficient == el.coefficient); 952 assert(d.sign == el.sign); 953 } 954 } 955 956 // float construction 957 unittest 958 { 959 static struct Test 960 { 961 double val; 962 ubyte sign; 963 int coefficient; 964 long exponent; 965 } 966 967 static struct SpecialTest 968 { 969 double val; 970 ubyte sign; 971 bool qNaN; 972 bool sNaN; 973 bool inf; 974 } 975 976 auto nonspecialTestValues = [ 977 Test(0.02, 0, 2, -2), 978 Test(0.00002, 0, 2, -5), 979 Test(1.02, 0, 102, -2), 980 Test(200.0, 0, 200, 0), 981 Test(1234.5678, 0, 12345678, -4), 982 Test(-1234.5678, 1, 12345678, -4), 983 Test(-1234, 1, 1234, 0), 984 ]; 985 986 auto specialTestValues = [ 987 SpecialTest(float.nan, 0, true, false, false), 988 SpecialTest(-float.nan, 1, true, false, false), 989 SpecialTest(float.infinity, 0, false, false, true), 990 SpecialTest(-float.infinity, 1, false, false, true), 991 ]; 992 993 foreach (el; nonspecialTestValues) 994 { 995 auto d = Decimal!()(el.val); 996 assert(d.coefficient == el.coefficient); 997 assert(d.sign == el.sign); 998 assert(d.exponent == el.exponent); 999 } 1000 1001 foreach (el; specialTestValues) 1002 { 1003 auto d = Decimal!()(el.val); 1004 assert(d.qNaN == el.qNaN); 1005 assert(d.sNaN == el.sNaN); 1006 assert(d.inf == el.inf); 1007 } 1008 } 1009 1010 // addition and subtraction 1011 @system 1012 unittest 1013 { 1014 static struct Test 1015 { 1016 string val1; 1017 string val2; 1018 string expected; 1019 bool invalidOperation; 1020 } 1021 1022 auto testPlusValues = [ 1023 Test("-0", "-0", "-0"), 1024 Test("-0", "0", "0"), 1025 Test("1", "2", "3"), 1026 Test("-5", "-3", "-8"), 1027 Test("5.75", "3.3", "9.05"), 1028 Test("1.23456789", "1.00000000", "2.23456789"), 1029 Test("10E5", "10E4", "1100000"), 1030 Test("0.9998", "0.0000", "0.9998"), 1031 Test("1", "0.0001", "1.0001"), 1032 Test("1", "0.00", "1.00"), 1033 Test("123.00", "3000.00", "3123.00"), 1034 Test("123.00", "3000.00", "3123.00"), 1035 Test("sNaN", "sNaN", "sNaN", true), 1036 Test("-sNaN", "sNaN", "-sNaN", true), 1037 Test("NaN", "sNaN", "sNaN", true), 1038 Test("sNaN", "1000", "sNaN", true), 1039 Test("1000", "sNaN", "sNaN", true), 1040 Test("1000", "NaN", "NaN"), 1041 Test("NaN", "NaN", "NaN"), 1042 Test("-NaN", "NaN", "-NaN"), 1043 Test("NaN", "Inf", "NaN"), 1044 Test("-NaN", "Inf", "-NaN"), 1045 Test("-Inf", "-Inf", "-Infinity"), 1046 Test("-Inf", "Inf", "NaN", true), 1047 Test("Inf", "-Inf", "NaN", true), 1048 Test("-Inf", "-1000", "-Infinity"), 1049 Test("-Inf", "1000", "-Infinity"), 1050 Test("Inf", "-1000", "Infinity"), 1051 Test("Inf", "1000", "Infinity") 1052 ]; 1053 1054 auto testMinusValues = [ 1055 Test("-0", "0", "-0"), 1056 Test("0", "-0", "0"), 1057 Test("0", "0", "0"), 1058 Test("1.3", "0.3", "1.0"), 1059 Test("1.3", "2.07", "-0.77"), 1060 Test("1.25", "1.25", "0.00"), 1061 Test("3", "-3.0", "6.0"), 1062 Test("1.23456789", "1.00000000", "0.23456789"), 1063 Test("10.2345679", "10.2345675", "0.0000004"), 1064 Test("0.999999999", "1", "-0.000000001"), 1065 Test("2.000E-3", "1.00200", "-1.000000"), 1066 Test("-Inf", "Inf", "-Infinity"), 1067 Test("-Inf", "1000", "-Infinity"), 1068 Test("1000", "-Inf", "Infinity"), 1069 Test("NaN", "Inf", "NaN"), 1070 Test("Inf", "NaN", "NaN"), 1071 Test("NaN", "NaN", "NaN"), 1072 Test("-NaN", "NaN", "-NaN"), 1073 Test("sNaN", "0", "sNaN", true), 1074 Test("sNaN", "-Inf", "sNaN", true), 1075 Test("sNaN", "NaN", "sNaN", true), 1076 Test("1000", "sNaN", "sNaN", true), 1077 Test("-sNaN", "sNaN", "-sNaN", true), 1078 Test("Inf", "Inf", "NaN", true), 1079 Test("-Inf", "-Inf", "NaN", true), 1080 ]; 1081 1082 foreach (el; testPlusValues) 1083 { 1084 auto v1 = decimal!(NoOp)(el.val1); 1085 auto v2 = decimal(el.val2); 1086 auto res = v1 + v2; 1087 assert(res.toString() == el.expected); 1088 assert(res.invalidOperation == el.invalidOperation); 1089 } 1090 1091 foreach (el; testMinusValues) 1092 { 1093 auto v1 = decimal!(NoOp)(el.val1); 1094 auto v2 = decimal(el.val2); 1095 auto res = v1 - v2; 1096 assert(res.toString() == el.expected); 1097 assert(res.invalidOperation == el.invalidOperation); 1098 } 1099 1100 // check that float and int compile 1101 assert(decimal("2.22") + 0.01 == decimal("2.23")); 1102 assert(decimal("2.22") + 1 == decimal("3.22")); 1103 1104 static struct CustomHook 1105 { 1106 enum Rounding roundingMode = Rounding.HalfUp; 1107 enum uint precision = 3; 1108 } 1109 1110 // rounding test on addition 1111 auto d1 = decimal!(CustomHook)("0.999E-2"); 1112 auto d2 = decimal!(CustomHook)("0.1E-2"); 1113 auto v = d1 + d2; 1114 assert(v.toString == "0.0110"); 1115 assert(v.inexact); 1116 assert(v.rounded); 1117 1118 // higher precision tests 1119 auto d3 = decimal!(HighPrecision)("10000e+9"); 1120 auto d4 = decimal!(HighPrecision)("7"); 1121 auto v2 = d3 - d4; 1122 assert(v2.toString() == "9999999999993"); 1123 1124 auto d5 = decimal!(HighPrecision)("1e-50"); 1125 auto d6 = decimal!(HighPrecision)("4e-50"); 1126 auto v3 = d5 + d6; 1127 assert(v3.toString() == "0.00000000000000000000000000000000000000000000000005"); 1128 } 1129 1130 // cmp and equals 1131 unittest 1132 { 1133 static struct Test 1134 { 1135 string val1; 1136 string val2; 1137 int expected; 1138 bool invalidOperation; 1139 } 1140 1141 auto testValues = [ 1142 Test("inf", "0", 1), 1143 Test("-inf", "0", -1), 1144 Test("-inf", "-inf", 0), 1145 Test("inf", "-inf", 1), 1146 Test("-inf", "inf", -1), 1147 Test("NaN", "1000", -1), 1148 Test("-NaN", "1000", -1), 1149 Test("1000", "NAN", 1), 1150 Test("1000", "-NAN", 1), 1151 Test("NaN", "inf", -1), 1152 Test("-NaN", "inf", -1), 1153 Test("-NaN", "NaN", -1), 1154 Test("NaN", "-NaN", 1), 1155 Test("-NaN", "-NaN", 0), 1156 Test("NaN", "NaN", 0), 1157 Test("sNaN", "NaN", -1, true), 1158 Test("sNaN", "-Inf", -1, true), 1159 Test("sNaN", "100", -1, true), 1160 Test("0", "-0", 0), 1161 Test("2.1", "3", -1), 1162 Test("2.1", "2.1", 0), 1163 Test("2.1", "2.10", 0), 1164 Test("3", "2.1", 1), 1165 Test("2.1", "-3", 1), 1166 Test("-3", "2.1", -1), 1167 Test("00", "00", 0), 1168 Test("70E-1", "7", 0), 1169 Test("8", "0.7E+1", 1), 1170 Test("-8.0", "7.0", -1), 1171 Test("80E-1", "-9", 1), 1172 Test("1E-15", "1", -1), 1173 Test("-0E2", "0", 0), 1174 Test("-8", "-70E-1", -1), 1175 Test("-12.1234", "-12.000000000", -1), 1176 ]; 1177 1178 foreach (el; testValues) 1179 { 1180 auto v1 = decimal!(NoOp)(el.val1); 1181 auto v2 = decimal!(NoOp)(el.val2); 1182 assert(v1.opCmp(v2) == el.expected); 1183 assert(v1.invalidOperation == el.invalidOperation); 1184 } 1185 1186 // make sure equals compiles, already covered behavior in 1187 // cmp tests 1188 assert(decimal("19.9999") != decimal("21.222222")); 1189 assert(decimal("22.000") == decimal("22")); 1190 assert(decimal("22.000") == 22); 1191 assert(decimal("22.2") == 22.2); 1192 } 1193 1194 // to string 1195 unittest 1196 { 1197 auto t = Decimal!()(); 1198 t.sign = 0; 1199 t.coefficient = 2_708; 1200 t.exponent = -2; 1201 assert(t.toString() == "27.08"); 1202 1203 auto t2 = Decimal!()(); 1204 t2.sign = 1; 1205 t2.coefficient = 1_953; 1206 t2.exponent = 0; 1207 assert(t2.toString() == "-1953"); 1208 1209 auto t3 = Decimal!()(); 1210 t3.sign = 0; 1211 t3.coefficient = 9_888_555_555; 1212 t3.exponent = -4; 1213 assert(t3.toString() == "988855.5555"); 1214 1215 auto t4 = Decimal!()("300088.44"); 1216 assert(t4.toString() == "300088.44"); 1217 1218 auto t5 = Decimal!()("30.5E10"); 1219 assert(t5.toString() == "305000000000"); 1220 1221 auto t6 = Decimal!()(10); 1222 assert(t6.toString() == "10"); 1223 1224 auto t7 = Decimal!()(12_345_678); 1225 assert(t7.toString() == "12345678"); 1226 1227 auto t8 = Decimal!()(1234.5678); 1228 assert(t8.toString() == "1234.5678"); 1229 1230 auto t9 = Decimal!()(0.1234); 1231 assert(t9.toString() == "0.1234"); 1232 1233 auto t10 = Decimal!()(1234.0); 1234 assert(t10.toString() == "1234"); 1235 1236 auto t11 = Decimal!()("1.2345678E-7"); 1237 assert(t11.toString() == "0.00000012345678"); 1238 1239 auto t12 = Decimal!()("INF"); 1240 assert(t12.toString() == "Infinity"); 1241 1242 auto t13 = Decimal!()("-INF"); 1243 assert(t13.toString() == "-Infinity"); 1244 1245 auto t14 = Decimal!()("NAN"); 1246 assert(t14.toString() == "NaN"); 1247 1248 auto t15 = Decimal!()("-NAN"); 1249 assert(t15.toString() == "-NaN"); 1250 } 1251 1252 // test rounding 1253 // @safe pure nothrow 1254 unittest 1255 { 1256 import std.exception : assertThrown; 1257 1258 static struct Test 1259 { 1260 ulong coefficient; 1261 ulong expected; 1262 bool inexact; 1263 bool rounded; 1264 } 1265 1266 static struct DownHook 1267 { 1268 enum uint precision = 5; 1269 enum Rounding roundingMode = Rounding.Down; 1270 } 1271 1272 static struct UpHook 1273 { 1274 enum uint precision = 5; 1275 enum Rounding roundingMode = Rounding.Up; 1276 } 1277 1278 static struct HalfUpHook 1279 { 1280 enum uint precision = 5; 1281 enum Rounding roundingMode = Rounding.HalfUp; 1282 } 1283 1284 auto downValues = [ 1285 Test(12345, 12345, false, false), 1286 Test(123449, 12344, true, true), 1287 Test(1234499999, 12344, true, true), 1288 Test(123451, 12345, true, true), 1289 Test(123450000001, 12345, true, true), 1290 Test(1234649999, 12346, true, true), 1291 Test(123465, 12346, true, true), 1292 Test(1234650001, 12346, true, true), 1293 Test(1234500, 12345, false, true) 1294 ]; 1295 auto upValues = [ 1296 Test(12345, 12345, false, false), 1297 Test(1234499, 12345, true, true), 1298 Test(123449999999, 12345, true, true), 1299 Test(123450000001, 12346, true, true), 1300 Test(123451, 12346, true, true), 1301 Test(1234649999, 12347, true, true), 1302 Test(123465, 12347, true, true), 1303 Test(123454, 12346, true, true), 1304 Test(1234500, 12345, false, true) 1305 ]; 1306 auto halfUpValues = [ 1307 Test(12345, 12345, false, false), 1308 Test(123449, 12345, true, true), 1309 Test(1234499, 12345, true, true), 1310 Test(12344999, 12345, true, true), 1311 Test(123451, 12345, true, true), 1312 Test(1234501, 12345, true, true), 1313 Test(123464999, 12346, true, true), 1314 Test(123465, 12347, true, true), 1315 Test(1234650001, 12347, true, true), 1316 Test(123456, 12346, true, true), 1317 Test(1234500, 12345, false, true) 1318 ]; 1319 1320 foreach (e; downValues) 1321 { 1322 auto d = Decimal!(DownHook)(e.coefficient); 1323 assert(d.coefficient == e.expected); 1324 assert(d.rounded == e.rounded); 1325 assert(d.inexact == e.inexact); 1326 } 1327 foreach (e; upValues) 1328 { 1329 auto d = Decimal!(UpHook)(e.coefficient); 1330 assert(d.coefficient == e.expected); 1331 assert(d.rounded == e.rounded); 1332 assert(d.inexact == e.inexact); 1333 } 1334 foreach (e; halfUpValues) 1335 { 1336 auto d = Decimal!(HalfUpHook)(e.coefficient); 1337 assert(d.coefficient == e.expected); 1338 assert(d.rounded == e.rounded); 1339 assert(d.inexact == e.inexact); 1340 } 1341 1342 // Test that the exponent is properly changed 1343 auto d1 = decimal!(HalfUpHook)("1.2345678E-7"); 1344 assert(d1.exponent == -11); 1345 1346 // test calling of defined hook methods 1347 static struct ThrowHook 1348 { 1349 enum uint precision = 5; 1350 enum Rounding roundingMode = Rounding.HalfUp; 1351 1352 static void onRounded(T)(T d) 1353 { 1354 throw new Exception("Rounded"); 1355 } 1356 } 1357 1358 assertThrown!Exception(Decimal!(ThrowHook)(1_234_567)); 1359 1360 // test rounding with BigInt 1361 static struct HigherHook 1362 { 1363 enum uint precision = 16; 1364 enum Rounding roundingMode = Rounding.HalfUp; 1365 } 1366 1367 auto d2 = decimal!(HigherHook)("10000000000000005"); 1368 assert(d2.rounded); 1369 assert(d2.toString() == "10000000000000010"); 1370 } 1371 1372 /** 1373 * Factory function 1374 */ 1375 auto decimal(Hook = Abort, R)(R r) 1376 if ((isForwardRange!R && 1377 isSomeChar!(ElementEncodingType!R) && 1378 !isInfinite!R) || isNumeric!R) 1379 { 1380 return Decimal!(Hook)(r); 1381 } 1382 1383 /// 1384 unittest 1385 { 1386 auto d1 = decimal(5.5); 1387 assert(d1.toString == "5.5"); 1388 1389 auto d2 = decimal("500.555"); 1390 } 1391 1392 /** 1393 * Rounding mode 1394 */ 1395 enum Rounding 1396 { 1397 /** 1398 * (Round toward 0; truncate.) The discarded digits are ignored; the result is unchanged. 1399 */ 1400 Down, 1401 /** 1402 * If the discarded digits represent greater than or equal to half (0.5) 1403 * of the value of a one in the next left position then the result coefficient 1404 * should be incremented by 1 (rounded up). Otherwise the discarded digits are ignored. 1405 */ 1406 HalfUp, 1407 /** 1408 * If the discarded digits represent greater than half (0.5) the value of a 1409 * one in the next left position then the result coefficient should be 1410 * incremented by 1 (rounded up). If they represent less than half, then the 1411 * result coefficient is not adjusted (that is, the discarded digits are ignored). 1412 * 1413 * Otherwise (they represent exactly half) the result coefficient is unaltered 1414 * if its rightmost digit is even, or incremented by 1 (rounded up) if its 1415 * rightmost digit is odd (to make an even digit). 1416 */ 1417 HalfEven, 1418 /** 1419 * If all of the discarded digits are zero or if the sign is 1 the result is 1420 * unchanged. Otherwise, the result coefficient should be incremented by 1 1421 * (rounded up). 1422 */ 1423 Ceiling, 1424 /** 1425 * If all of the discarded digits are zero or if the sign is 0 the result is 1426 * unchanged. Otherwise, the sign is 1 and the result coefficient should be 1427 * incremented by 1. 1428 */ 1429 Floor, 1430 /** 1431 * If the discarded digits represent greater than half (0.5) of the value of 1432 * a one in the next left position then the result coefficient should be 1433 * incremented by 1 (rounded up). Otherwise (the discarded digits are 0.5 or 1434 * less) the discarded digits are ignored. 1435 */ 1436 HalfDown, 1437 /** 1438 * (Round away from 0.) If all of the discarded digits are zero the result is 1439 * unchanged. Otherwise, the result coefficient should be incremented by 1 (rounded up). 1440 */ 1441 Up, 1442 /** 1443 * (Round zero or five away from 0.) The same as round-up, except that rounding 1444 * up only occurs if the digit to be rounded up is 0 or 5, and after overflow 1445 * the result is the same as for round-down. 1446 */ 1447 ZeroFiveUp 1448 } 1449 1450 /** 1451 * Will halt program on division by zero, invalid operations, 1452 * overflows, and underflows 1453 * 1454 * Has 16 significant digits, rounds half up 1455 */ 1456 struct Abort 1457 { 1458 /// 1459 enum Rounding roundingMode = Rounding.HalfUp; 1460 /** 1461 * A precision of 9 allows all possible the results of +,-,*, and / 1462 * to fit into a `ulong` with no issues. 1463 */ 1464 enum uint precision = 9; 1465 1466 /// 1467 static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T)) 1468 { 1469 assert(0, "Division by zero"); 1470 } 1471 1472 /// 1473 static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T)) 1474 { 1475 assert(0, "Invalid operation"); 1476 } 1477 1478 /// 1479 static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T)) 1480 { 1481 assert(0, "Overflow"); 1482 } 1483 1484 /// 1485 static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T)) 1486 { 1487 assert(0, "Underflow"); 1488 } 1489 } 1490 1491 /** 1492 * Same as abort, but offers 64 significant digits 1493 * 1494 * Note: Using any precision over `9` is an order of magnitude slower 1495 * due to implementation constraints. Only use this if you really need 1496 * data that precise 1497 */ 1498 static struct HighPrecision 1499 { 1500 enum Rounding roundingMode = Rounding.HalfUp; 1501 enum uint precision = 64; 1502 1503 /// 1504 static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T)) 1505 { 1506 assert(0, "Division by zero"); 1507 } 1508 1509 /// 1510 static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T)) 1511 { 1512 assert(0, "Invalid operation"); 1513 } 1514 1515 /// 1516 static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T)) 1517 { 1518 assert(0, "Overflow"); 1519 } 1520 1521 /// 1522 static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T)) 1523 { 1524 assert(0, "Underflow"); 1525 } 1526 } 1527 1528 /** 1529 * Will throw exceptions on division by zero, invalid operations, 1530 * overflows, and underflows 1531 * 1532 * Has 16 significant digits, rounds half up 1533 */ 1534 struct Throw 1535 { 1536 /// 1537 enum Rounding roundingMode = Rounding.HalfUp; 1538 /// 1539 enum uint precision = 9; 1540 1541 /// 1542 static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T)) 1543 { 1544 throw new DivisionByZero(); 1545 } 1546 1547 /// 1548 static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T)) 1549 { 1550 throw new InvalidOperation(); 1551 } 1552 1553 /// 1554 static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T)) 1555 { 1556 throw new Overflow(); 1557 } 1558 1559 /// 1560 static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T)) 1561 { 1562 throw new Underflow(); 1563 } 1564 } 1565 1566 /** 1567 * Does nothing on invalid operations except the proper flags 1568 * 1569 * Has 16 significant digits, rounds half up 1570 */ 1571 struct NoOp 1572 { 1573 /// 1574 enum Rounding roundingMode = Rounding.HalfUp; 1575 /// 1576 enum uint precision = 9; 1577 } 1578 1579 /** 1580 * Thrown when using $(LREF Throw) and division by zero occurs 1581 */ 1582 class DivisionByZero : Exception 1583 { 1584 /++ 1585 Params: 1586 msg = The message for the exception. 1587 file = The file where the exception occurred. 1588 line = The line number where the exception occurred. 1589 next = The previous exception in the chain of exceptions, if any. 1590 +/ 1591 this(string msg, string file = __FILE__, size_t line = __LINE__, 1592 Throwable next = null) @nogc @safe pure nothrow 1593 { 1594 super(msg, file, line, next); 1595 } 1596 1597 /++ 1598 Params: 1599 msg = The message for the exception. 1600 next = The previous exception in the chain of exceptions. 1601 file = The file where the exception occurred. 1602 line = The line number where the exception occurred. 1603 +/ 1604 this(string msg, Throwable next, string file = __FILE__, 1605 size_t line = __LINE__) @nogc @safe pure nothrow 1606 { 1607 super(msg, file, line, next); 1608 } 1609 } 1610 1611 /** 1612 * Thrown when using $(LREF Throw) and an invalid operation occurs 1613 */ 1614 class InvalidOperation : Exception 1615 { 1616 /++ 1617 Params: 1618 msg = The message for the exception. 1619 file = The file where the exception occurred. 1620 line = The line number where the exception occurred. 1621 next = The previous exception in the chain of exceptions, if any. 1622 +/ 1623 this(string msg, string file = __FILE__, size_t line = __LINE__, 1624 Throwable next = null) @nogc @safe pure nothrow 1625 { 1626 super(msg, file, line, next); 1627 } 1628 1629 /++ 1630 Params: 1631 msg = The message for the exception. 1632 next = The previous exception in the chain of exceptions. 1633 file = The file where the exception occurred. 1634 line = The line number where the exception occurred. 1635 +/ 1636 this(string msg, Throwable next, string file = __FILE__, 1637 size_t line = __LINE__) @nogc @safe pure nothrow 1638 { 1639 super(msg, file, line, next); 1640 } 1641 } 1642 1643 /** 1644 * Thrown when using $(LREF Throw) and overflow occurs 1645 */ 1646 class Overflow : Exception 1647 { 1648 /++ 1649 Params: 1650 msg = The message for the exception. 1651 file = The file where the exception occurred. 1652 line = The line number where the exception occurred. 1653 next = The previous exception in the chain of exceptions, if any. 1654 +/ 1655 this(string msg, string file = __FILE__, size_t line = __LINE__, 1656 Throwable next = null) @nogc @safe pure nothrow 1657 { 1658 super(msg, file, line, next); 1659 } 1660 1661 /++ 1662 Params: 1663 msg = The message for the exception. 1664 next = The previous exception in the chain of exceptions. 1665 file = The file where the exception occurred. 1666 line = The line number where the exception occurred. 1667 +/ 1668 this(string msg, Throwable next, string file = __FILE__, 1669 size_t line = __LINE__) @nogc @safe pure nothrow 1670 { 1671 super(msg, file, line, next); 1672 } 1673 } 1674 1675 /** 1676 * Thrown when using $(LREF Throw) and underflow occurs 1677 */ 1678 class Underflow : Exception 1679 { 1680 /++ 1681 Params: 1682 msg = The message for the exception. 1683 file = The file where the exception occurred. 1684 line = The line number where the exception occurred. 1685 next = The previous exception in the chain of exceptions, if any. 1686 +/ 1687 this(string msg, string file = __FILE__, size_t line = __LINE__, 1688 Throwable next = null) @nogc @safe pure nothrow 1689 { 1690 super(msg, file, line, next); 1691 } 1692 1693 /++ 1694 Params: 1695 msg = The message for the exception. 1696 next = The previous exception in the chain of exceptions. 1697 file = The file where the exception occurred. 1698 line = The line number where the exception occurred. 1699 +/ 1700 this(string msg, Throwable next, string file = __FILE__, 1701 size_t line = __LINE__) @nogc @safe pure nothrow 1702 { 1703 super(msg, file, line, next); 1704 } 1705 } 1706 1707 /* 1708 * Get the number of digits in the decimal representation of a number 1709 */ 1710 private auto numberOfDigits(T)(T x) 1711 { 1712 import std.algorithm.comparison : max; 1713 import std.math : floor, log10; 1714 1715 static if (isIntegral!T) 1716 { 1717 static if (is(Signed!T == T)) 1718 { 1719 import std.math : abs; 1720 x = abs(x); 1721 } 1722 1723 return (cast(uint) x.log10.floor.max(0)) + 1; 1724 } 1725 else 1726 { 1727 uint digits; 1728 1729 if (x < 0) 1730 x *= -1; 1731 1732 while (x > 0) 1733 { 1734 ++digits; 1735 x /= 10; 1736 } 1737 1738 return max(digits, 1); 1739 } 1740 } 1741 1742 @safe @nogc pure nothrow unittest 1743 { 1744 assert(numberOfDigits(0) == 1); 1745 assert(numberOfDigits(1) == 1); 1746 assert(numberOfDigits(1_000UL) == 4); 1747 assert(numberOfDigits(-1_000L) == 4); 1748 assert(numberOfDigits(1_000_000) == 7); 1749 assert(numberOfDigits(-1_000_000) == 7); 1750 assert(numberOfDigits(123_456) == 6); 1751 } 1752 1753 1754 @system pure unittest 1755 { 1756 import std.bigint; 1757 1758 assert(numberOfDigits(BigInt("0")) == 1); 1759 assert(numberOfDigits(BigInt("1")) == 1); 1760 assert(numberOfDigits(BigInt("1_000")) == 4); 1761 assert(numberOfDigits(BigInt("-1_000")) == 4); 1762 assert(numberOfDigits(BigInt("1_000_000")) == 7); 1763 assert(numberOfDigits(BigInt("123_456")) == 6); 1764 assert(numberOfDigits(BigInt("123_456")) == 6); 1765 assert(numberOfDigits(BigInt("123_456_789_101_112_131_415_161")) == 24); 1766 } 1767 1768 /* 1769 * Detect whether $(D X) is an enum type, or manifest constant. 1770 */ 1771 private template isEnum(X...) if (X.length == 1) 1772 { 1773 static if (is(X[0] == enum)) 1774 { 1775 enum isEnum = true; 1776 } 1777 else static if (!is(X[0]) && 1778 !is(typeof(X[0]) == void) && 1779 !isFunction!(X[0])) 1780 { 1781 enum isEnum = 1782 !is(typeof({ auto ptr = &X[0]; })) 1783 && !is(typeof({ enum off = X[0].offsetof; })); 1784 } 1785 else 1786 enum isEnum = false; 1787 }