1 /** 2 * Quick_Start: 3 * --- 4 * import stdxdecimal; 5 * 6 * void main() 7 * { 8 * auto d1 = decimal("1.23E-10"); 9 * d1 -= decimal("2.00E-10"); 10 * assert(d1.toString() == "-0.000000000077"); 11 * } 12 * --- 13 * 14 * This module defines an exact decimal type, `Decimal`, to a specific number of digits. 15 * This is designed to be a drop in replacement for built-in floating point numbers, 16 * allowing all the same possible operations. 17 * 18 * Floating point numbers (`float`, `double`, `real`) are inherently inaccurate because 19 * they $(HTTPS en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems, cannot represent) 20 * all possible numbers with a decimal part. `Decimal` on the other hand, is able to 21 * represent all possible numbers with a decimal part (limited by memory size and `Hook`). 22 * 23 * Adapted from the specification of the 24 * $(HTTP speleotrove.com/decimal/decarith.html, General Decimal Arithmetic). 25 * 26 * Custom_Behavior: 27 * The behavior of `Decimal` is controlled by the template parameter `Hook`, 28 * which can be a user defined type or one of the `Hook`s provided by this 29 * module. 30 * 31 * The following behaviors are controlled by `Hook`: 32 * 33 * $(UL 34 * $(LI The number of significant digits to store.) 35 * $(LI The rounding method.) 36 * $(LI The min and max exponents.) 37 * $(LI What to do when exceptional conditions arise.) 38 * ) 39 * 40 * The following predefined `Hook`s are available: 41 * 42 * $(TABLE 43 * $(TR 44 * $(TD $(LREF Abort)) 45 * $(TD `precision` is set to 9, rounding is `HalfUp`, and the program 46 * will `assert(0)` on divsion by zero, overflow, underflow, and 47 * invalid operations. 48 * ) 49 * ) 50 * $(TR 51 * $(TD $(LREF Throw)) 52 * $(TD `precision` is set to 9, rounding is `HalfUp`, and the program 53 * will throw an exception on divsion by zero, overflow, underflow, and 54 * invalid operations. 55 * ) 56 * ) 57 * $(TR 58 * $(TD $(LREF HighPrecision)) 59 * $(TD `precision` is set to 64, rounding is `HalfUp`, and the program 60 * will `assert(0)` on divsion by zero, overflow, underflow, and 61 * invalid operations. 62 * ) 63 * ) 64 * $(TR 65 * $(TD $(LREF NoOp)) 66 * $(TD `precision` is set to 9, rounding is `HalfUp`, and nothing will 67 * happen on exceptional conditions. 68 * ) 69 * ) 70 * ) 71 * 72 * Percision_and_Rounding: 73 * `Decimal` accurately stores as many as `Hook.precision` significant digits. 74 * Once the number of digits `> Hook.precision`, then the number is rounded. 75 * Rounding is performed according to the rules laid out in $(LREF RoundingMode). 76 * 77 * By default, the precision is 9, and the rounding mode is `RoundingMode.HalfUp`. 78 * 79 * `Hook.precision` must be `<= uint.max - 1` and `> 1`. 80 * 81 * Note_On_Speed: 82 * The more digits of precision you define in hook, the slower many operations 83 * will become. It's recommended that you use the least amount of precision 84 * necessary for your code. 85 * 86 * Exceptional_Conditions: 87 * Certain operations will cause a `Decimal` to enter into an invalid state, 88 * e.g. dividing by zero. When this happens, Decimal does two things 89 * 90 * $(OL 91 * $(LI Sets a public `bool` variable to `true`.) 92 * $(LI Calls a specific function in `Hook`, if it exists, with the 93 * operation's result as the only parameter.) 94 * ) 95 * 96 * The following table lists all of the conditions 97 * 98 * $(TABLE 99 * $(THEAD 100 * $(TR 101 * $(TH Name) 102 * $(TH Flag) 103 * $(TH Method) 104 * $(TH Description) 105 * ) 106 * ) 107 * $(TBODY 108 * $(TR 109 * $(TD Clamped) 110 * $(TD `clamped`) 111 * $(TD `onClamped`) 112 * $(TD Occurs when the exponent has been altered to fit in-between 113 * `Hook.maxExponent` and `Hook.minExponent`. 114 * ) 115 * ) 116 * $(TR 117 * $(TD Inexact) 118 * $(TD `inexact`) 119 * $(TD `onInexact`) 120 * $(TD Occurs when the result of an operation is not perfectly accurate. 121 * Mostly occurs when rounding removed non-zero digits. 122 * ) 123 * ) 124 * $(TR 125 * $(TD Invalid Operation) 126 * $(TD `invalidOperation`) 127 * $(TD `onInvalidOperation`) 128 * $(TD Flagged when an operation makes no sense, e.g. multiplying `0` 129 * and `Infinity` or add -Infinity to Infinity. 130 * ) 131 * ) 132 * $(TR 133 * $(TD Division by Zero) 134 * $(TD `divisionByZero`) 135 * $(TD `onDivisionByZero`) 136 * $(TD Specific invalid operation. Occurs whenever the dividend of a 137 * division or modulo is equal to zero. 138 * ) 139 * ) 140 * $(TR 141 * $(TD Rounded) 142 * $(TD `rounded`) 143 * $(TD `onRounded`) 144 * $(TD Occurs when the `Decimal`'s result had more than `Hook.precision` 145 * significant digits and was reduced. 146 * ) 147 * ) 148 * $(TR 149 * $(TD Subnormal) 150 * $(TD `subnormal`) 151 * $(TD `onSubnormal`) 152 * $(TD Flagged when the exponent is less than `Hook.maxExponent` but the 153 * digits of the `Decimal` are not inexact. 154 * ) 155 * ) 156 * $(TR 157 * $(TD Overflow) 158 * $(TD `overflow`) 159 * $(TD `onOverflow`) 160 * $(TD Not to be confused with integer overflow, this is flagged when 161 * the exponent of the result of an operation would have been above 162 * `Hook.maxExponent` and the result is inexact. Inexact and Rounded 163 * are always set with this flag. 164 * ) 165 * ) 166 * $(TR 167 * $(TD Underflow) 168 * $(TD `underflow`) 169 * $(TD `onUnderflow`) 170 * $(TD Not to be confused with integer underflow, this is flagged when 171 * the exponent of the result of an operation would have been below 172 * `Hook.minExponent`. Inexact, Rounded, and Subnormal are always set with 173 * this flag. 174 * ) 175 * ) 176 * ) 177 * ) 178 * 179 * Each function documentation lists the specific states that will led to one 180 * of these flags. 181 * 182 * Differences_From_The_Specification: 183 * $(UL 184 * $(LI There's no concept of a Signaling NaN in this module.) 185 * $(LI There's no concept of a Diagnostic NaN in this module.) 186 * $(LI `compare`, implemented as `opCmp`, does not propagate `NaN` due 187 * to D's `opCmp` semantics.) 188 * ) 189 * 190 * Version: 191 * `v0.5`. Still work in progress. For missing features, see `README.md` 192 * 193 * License: 194 * $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 195 * 196 * Authors: 197 * Jack Stouffer 198 */ 199 module stdxdecimal; 200 201 version(unittest) { import std.stdio; } 202 import std.range.primitives; 203 import std.traits; 204 import std.bigint; 205 206 /** 207 * A exact decimal type, accurate to `Hook.precision` digits. Designed to be a 208 * drop in replacement for floating points. 209 * 210 * Behavior is defined by `Hook`. See the module overview for more information. 211 */ 212 struct Decimal(Hook = Abort) 213 { 214 import std.experimental.allocator.common : stateSize; 215 216 static assert( 217 (hasMember!(Hook, "precision") && is(typeof(Hook.precision) : uint)) 218 || !hasMember!(Hook, "precision"), 219 "Hook.precision must be implicitly convertible to uint" 220 ); 221 static assert( 222 (hasMember!(Hook, "precision") && isEnum!(Hook.precision)) 223 || !hasMember!(Hook, "precision"), 224 "Hook.precision must be readable at compile-time" 225 ); 226 static assert( 227 (hasMember!(Hook, "precision") && hook.precision > 1) 228 || !hasMember!(Hook, "precision"), 229 "Hook.precision is too small (must be at least 2)" 230 ); 231 static assert( 232 (hasMember!(Hook, "precision") && hook.precision < uint.max) 233 || !hasMember!(Hook, "precision"), 234 "Hook.precision must be < uint.max" 235 ); 236 static assert( 237 (hasMember!(Hook, "roundingMode") && is(typeof(Hook.roundingMode) == Rounding)) 238 || !hasMember!(Hook, "precision"), 239 "Hook.roundingMode must be of type Rounding" 240 ); 241 static assert( 242 (hasMember!(Hook, "precision") && isEnum!(Hook.roundingMode)) 243 || !hasMember!(Hook, "precision"), 244 "Hook.roundingMode must be readable at compile-time" 245 ); 246 static assert( 247 (hasMember!(Hook, "maxExponent") && isEnum!(Hook.maxExponent)) 248 || !hasMember!(Hook, "maxExponent"), 249 "Hook.maxExponent must be readable at compile-time" 250 ); 251 static assert( 252 (hasMember!(Hook, "minExponent") && isEnum!(Hook.minExponent)) 253 || !hasMember!(Hook, "minExponent"), 254 "Hook.minExponent must be readable at compile-time" 255 ); 256 257 // result of bitfields separated out so the properties can be public 258 private ushort signAndFlags; 259 @property void sign(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 1U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))1U);} 260 @property void isNan(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 2U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))2U);} 261 @property void isInf(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 4U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))4U);} 262 @property void clamped(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 8U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))8U);} 263 @property void divisionByZero(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 16U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))16U);} 264 @property void inexact(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 32U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))32U);} 265 @property void invalidOperation(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 64U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))64U);} 266 @property void overflow(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 128U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))128U);} 267 @property void rounded(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 256U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))256U);} 268 @property void subnormal(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 512U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))512U);} 269 @property void underflow(bool v) @safe pure nothrow @nogc { if (v) signAndFlags |= 1024U;else signAndFlags &= cast(typeof(signAndFlags))(-1-cast(typeof(signAndFlags))1024U);} 270 271 package: 272 BigInt coefficient; 273 int exponent; 274 275 // choose between default or defined parameters 276 static if (hasMember!(Hook, "precision")) 277 enum uint precision = Hook.precision; 278 else 279 enum uint precision = 16; 280 281 static if (hasMember!(Hook, "roundingMode")) 282 enum Rounding roundingMode = Hook.roundingMode; 283 else 284 enum Rounding roundingMode = Rounding.HalfUp; 285 286 static if (hasMember!(Hook, "maxExponent") && isEnum!(Hook.maxExponent)) 287 enum int maxExponent = Hook.maxExponent; 288 else 289 enum int maxExponent = 999; 290 291 static if (hasMember!(Hook, "minExponent") && isEnum!(Hook.minExponent)) 292 enum int minExponent = Hook.minExponent; 293 else 294 enum int minExponent = -999; 295 296 static assert( 297 minExponent < maxExponent, 298 "minExponent must be less than maxExponent" 299 ); 300 301 enum hasClampedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onClamped(d); }); 302 enum hasRoundedMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onRounded(d); }); 303 enum hasInexactMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInexact(d); }); 304 enum hasDivisionByZeroMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onDivisionByZero(d); }); 305 enum hasInvalidOperationMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onInvalidOperation(d); }); 306 enum hasOverflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onOverflow(d); }); 307 enum hasSubnormalMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onSubnormal(d); }); 308 enum hasUnderflowMethod = __traits(compiles, { auto d = Decimal!(Hook)(0); hook.onUnderflow(d); }); 309 310 /* 311 rounds num via `Hook`s rounding mode. 312 313 Rounded is always set if the coefficient is changed 314 315 Inexact is set if the rounded digits were non-zero 316 */ 317 auto round(T)(T num) pure 318 { 319 version (D_InlineAsm_X86) {} 320 else 321 { 322 enum BigInt max = BigInt(10) ^^ precision; 323 if (num < max) 324 return num; 325 } 326 327 auto digits = numberOfDigits(num); 328 if (digits <= precision) 329 return num; 330 331 Unqual!(T) lastDigit; 332 333 // TODO: as soon as inexact == true, we can quit the 334 // loops and do a single division 335 static if (roundingMode == Rounding.Down) 336 { 337 while (digits > precision) 338 { 339 if (!inexact) 340 lastDigit = num % 10; 341 342 num /= 10; 343 344 --digits; 345 ++exponent; 346 347 if (!inexact && lastDigit != 0) 348 inexact = true; 349 } 350 } 351 else static if (roundingMode == Rounding.Up) 352 { 353 while (digits > precision) 354 { 355 if (!inexact) 356 lastDigit = num % 10; 357 358 num /= 10; 359 360 --digits; 361 ++exponent; 362 363 if (!inexact && lastDigit != 0) 364 inexact = true; 365 } 366 367 // If all of the discarded digits are zero the result is unchanged 368 if (inexact) 369 ++num; 370 } 371 else static if (roundingMode == Rounding.HalfUp) 372 { 373 while (digits > precision + 1) 374 { 375 if (!inexact) 376 lastDigit = num % 10; 377 378 num /= 10; 379 380 --digits; 381 ++exponent; 382 383 if (!inexact && lastDigit != 0) 384 inexact = true; 385 } 386 387 lastDigit = num % 10; 388 num /= 10; 389 390 if (lastDigit != 0) 391 inexact = true; 392 ++exponent; 393 394 if (lastDigit >= 5) 395 ++num; 396 } 397 else 398 { 399 static assert(0, "Not implemented"); 400 } 401 402 rounded = true; 403 404 static if (hasInexactMethod) 405 if (inexact) 406 hook.onInexact(this); 407 408 static if (hasRoundedMethod) 409 hook.onRounded(this); 410 411 return num; 412 } 413 414 /* 415 Separated into its own function for readability as well as 416 allowing opCmp to skip the rounding step 417 */ 418 ref Decimal!(Hook) addImpl(string op, bool doRound, T)(T rhs) 419 { 420 import std.algorithm.comparison : min; 421 import std.math : abs; 422 import std.conv : to; 423 424 bool rhsSign; 425 426 static if (op == "-") 427 rhsSign = rhs.sign == 0 ? 1 : 0; 428 else 429 rhsSign = rhs.sign; 430 431 if (isNan || rhs.isNan) 432 { 433 isNan = true; 434 isInf = false; 435 436 // the sign of the first isNan is simply propagated 437 if (isNan) 438 { 439 sign = sign; 440 } 441 else if (rhs.isNan) 442 { 443 static if (op == "-") 444 sign = 0 ? 1 : 0; 445 else 446 sign = sign; 447 } 448 449 coefficient = 0; 450 exponent = 0; 451 return this; 452 } 453 454 if (isInf && rhs.isInf) 455 { 456 if (sign == 1 && rhsSign == 1) 457 { 458 sign = 1; 459 isInf = true; 460 return this; 461 } 462 463 if (sign == 0 && rhsSign == 0) 464 { 465 isInf = true; 466 return this; 467 } 468 469 // -Inf + Inf makes no sense 470 isNan = true; 471 isInf = false; 472 coefficient = 0; 473 exponent = 0; 474 sign = 0; 475 invalidOperation = true; 476 static if (hasInvalidOperationMethod) 477 hook.onInvalidOperation(this); 478 return this; 479 } 480 481 if (isInf) 482 { 483 isInf = true; 484 sign = sign; 485 coefficient = 0; 486 exponent = 0; 487 return this; 488 } 489 490 if (rhs.isInf) 491 { 492 isInf = true; 493 sign = rhsSign; 494 coefficient = 0; 495 exponent = 0; 496 return this; 497 } 498 499 BigInt alignedCoefficient = coefficient; 500 BigInt rhsAlignedCoefficient = rhs.coefficient; 501 502 if (exponent != rhs.exponent) 503 { 504 long diff; 505 506 if (exponent > rhs.exponent) 507 { 508 diff = abs(exponent - rhs.exponent); 509 alignedCoefficient *= BigInt(10) ^^ diff; 510 } 511 else 512 { 513 diff = abs(rhs.exponent - exponent); 514 rhsAlignedCoefficient *= BigInt(10) ^^ diff; 515 } 516 } 517 518 exponent = min(exponent, rhs.exponent); 519 // If the signs of the operands differ then the smaller aligned coefficient 520 // is subtracted from the larger; otherwise they are added. 521 if (sign == rhsSign) 522 { 523 coefficient = alignedCoefficient + rhsAlignedCoefficient; 524 } 525 else 526 { 527 if (alignedCoefficient >= rhsAlignedCoefficient) 528 { 529 coefficient = alignedCoefficient - rhsAlignedCoefficient; 530 } 531 else 532 { 533 coefficient = rhsAlignedCoefficient - alignedCoefficient; 534 } 535 } 536 537 if (coefficient != 0) 538 { 539 // the sign of the result is the sign of the operand having 540 // the larger absolute value. 541 if (alignedCoefficient >= rhsAlignedCoefficient) 542 sign = sign; 543 else 544 sign = rhsSign; 545 } 546 else 547 { 548 if (sign == 1 && rhsSign == 1) 549 sign = 1; 550 else 551 sign = 0; 552 553 static if (roundingMode == Rounding.Floor) 554 if (sign != rhsSign) 555 sign = 1; 556 } 557 558 static if (doRound) 559 coefficient = round(coefficient); 560 561 return this; 562 } 563 564 public: 565 /** 566 * `hook` is a member variable if it has state, or an alias for `Hook` 567 * otherwise. 568 */ 569 static if (stateSize!Hook > 0) 570 Hook hook; 571 else 572 alias hook = Hook; 573 574 /** 575 * Constructs an exact decimal type from a built in number 576 * 577 * Params: 578 * num = the number to convert to exact decimal 579 * 580 * Note: 581 * Using `float` types for construction is less accurate than using a string 582 * representation due to floating point inaccuracy. If possible, it's always 583 * better to use string construction. 584 */ 585 this(T)(T num) pure // for some reason doesn't infer pure 586 if (isNumeric!T) 587 { 588 opAssign(num); 589 } 590 591 /** 592 * Converts a string representing a number to an exact decimal. 593 * 594 * If the string does not represent a number, then the result is `NaN` 595 * and `invalidOperation` is `true`. 596 * 597 * Params: 598 * str = The string to convert from 599 * 600 * Specification: 601 * ------- 602 * sign ::= + | - 603 * digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 604 * indicator ::= e | E 605 * digits ::= digit [digit]... 606 * decimal-part ::= digits . [digits] | [.] digits 607 * exponent-part ::= indicator [sign] digits 608 * infinity ::= Infinity | Inf 609 * nan ::= NaN 610 * numeric-value ::= decimal-part [exponent-part] | infinity 611 * numeric-string ::= [sign] numeric-value | [sign] nan 612 * ------- 613 * 614 * Exceptional_Conditions: 615 * `invalidOperation` is flagged when `str` is not a valid string 616 */ 617 this(S)(S str) 618 if (isForwardRange!S && isSomeChar!(ElementEncodingType!S) && !isInfinite!S && !isSomeString!S) 619 { 620 import std.algorithm.comparison : among, equal; 621 import std.algorithm.iteration : filter, map; 622 import std.algorithm.searching : startsWith; 623 import std.ascii : isDigit, toLower; 624 import std.utf : byChar, byCodeUnit; 625 626 static bool asciiCmp(S1)(S1 a, string b) 627 { 628 return a.map!toLower.equal(b.byChar.map!toLower); 629 } 630 631 // TODO: filter out underscores 632 633 if (str.empty) 634 { 635 isNan = true; 636 return; 637 } 638 639 immutable frontResult = str.front; 640 bool sawDecimal = false; 641 bool sawExponent = false; 642 bool sawExponentSign = false; 643 byte exponentSign; 644 int sciExponent = 0; 645 646 if (frontResult == '+') 647 { 648 str.popFront; 649 } 650 else if (frontResult == '-') 651 { 652 sign = 1; 653 str.popFront; 654 } 655 656 if (str.empty) 657 { 658 sign = 0; 659 goto Lerr; 660 } 661 662 if (str.among!((a, b) => asciiCmp(a.save, b)) 663 ("inf", "infinity")) 664 { 665 isInf = true; 666 return; 667 } 668 669 // having numbers after nan is valid in the spec 670 if (str.save.map!toLower.startsWith("nan".byChar)) 671 { 672 isNan = true; 673 return; 674 } 675 676 // leading zeros 677 while (!str.empty && str.front == '0') 678 str.popFront; 679 680 for (; !str.empty; str.popFront) 681 { 682 auto digit = str.front; 683 684 if (isDigit(digit)) 685 { 686 if (!sawExponent) 687 { 688 coefficient *= 10; 689 coefficient += cast(uint) (digit - '0'); 690 } 691 692 if (sawDecimal && !sawExponent) 693 exponent--; 694 695 if (sawExponent) 696 { 697 while (!str.empty) 698 { 699 if (!isDigit(str.front)) 700 goto Lerr; 701 702 sciExponent += cast(uint) (str.front - '0'); 703 if (!str.empty) 704 { 705 str.popFront; 706 if (!str.empty) 707 sciExponent *= 10; 708 } 709 } 710 711 if (sawExponentSign && exponentSign == -1) 712 sciExponent *= -1; 713 714 exponent += sciExponent; 715 716 if (str.empty) 717 { 718 coefficient = round(coefficient); 719 return; 720 } 721 } 722 723 continue; 724 } 725 726 if (digit == '+' || digit == '-') 727 { 728 // already have exponent sign, bad input so cancel out 729 if (sawExponentSign) 730 goto Lerr; 731 732 if (sawExponent) 733 { 734 if (digit == '-') 735 exponentSign = -1; 736 sawExponentSign = true; 737 } 738 else 739 { // no exponent yet, bad input so cancel out 740 goto Lerr; 741 } 742 743 continue; 744 } 745 746 if (digit == '.') 747 { 748 // already have decimal, bad input so cancel out 749 if (sawDecimal) 750 goto Lerr; 751 752 sawDecimal = true; 753 continue; 754 } 755 756 if (digit.toLower == 'e') 757 { 758 // already have exponent, bad input so cancel out 759 if (sawExponent) 760 goto Lerr; 761 762 sawExponent = true; 763 continue; 764 } 765 766 goto Lerr; 767 } 768 769 coefficient = round(coefficient); 770 return; 771 772 Lerr: 773 isNan = true; 774 coefficient = 0; 775 exponent = 0; 776 777 invalidOperation = true; 778 static if (hasInvalidOperationMethod) 779 hook.onInvalidOperation(this); 780 781 return; 782 } 783 784 /// ditto 785 this(S)(S str) pure if (isSomeString!S) 786 { 787 // hack to allow pure annotation for immutable construction 788 // see Issue 17330 789 import std.utf : byCodeUnit; 790 this(str.byCodeUnit); 791 } 792 793 /** 794 * Changes the value of this decimal to the value of a built-in number. 795 * Also resets all exceptional condition flags. 796 * 797 * Params: 798 * num = the number to convert to exact decimal 799 * 800 * Note: 801 * Using `float` types for construction is less accurate than using a string 802 * representation due to floating point inaccuracy. If possible, it's always 803 * better to use string construction. 804 */ 805 auto opAssign(T)(T num) if (isNumeric!T) 806 { 807 resetFlags(); 808 809 // the behavior of conversion from built-in number types 810 // isn't covered by the spec, so we can do whatever we 811 // want here 812 static if (isIntegral!T) 813 { 814 static if (isSigned!T) 815 { 816 import std.math : abs; 817 818 // work around int.min bug where abs(int.min) == int.min 819 static if (T.sizeof <= int.sizeof) 820 coefficient = abs(cast(long) num); 821 else 822 coefficient = abs(num); 823 824 sign = num >= 0 ? 0 : 1; 825 } 826 else 827 { 828 coefficient = num; 829 } 830 } 831 else 832 { 833 import std.math : abs, isInfinity, isIdentical, isNaN; 834 835 Unqual!T val = num; 836 837 if (isInfinity(val)) 838 { 839 isInf = true; 840 sign = val > 0 ? 0 : 1; 841 return this; 842 } 843 844 if (isNaN(val)) 845 { 846 isNan = true; 847 sign = isIdentical(val, T.nan) ? 0 : 1; 848 return this; 849 } 850 851 sign = val >= 0 ? 0 : 1; 852 val = abs(val); 853 854 // while the number still has a fractional part, multiply by 10, 855 // counting each time until no fractional part 856 Unqual!T fraction = val - (cast(long) val); 857 while (fraction > 0) 858 { 859 --exponent; 860 val *= 10; 861 fraction = val - (cast(long) val); 862 } 863 864 coefficient = cast(size_t) val; 865 } 866 867 coefficient = round(coefficient); 868 return this; 869 } 870 871 /** 872 * Performs a binary operation between two decimals, or a decimal and 873 * a built in number. 874 * 875 * The result has the hook of the left hand side. On non-assignment 876 * operations, invalid operations do not effect the left hand side of 877 * the operation, only the result. 878 * 879 * When the right hand side is a built-in numeric type, the default 880 * hook `Abort` is used for its decimal representation in the operation. 881 * 882 * Params: 883 * rhs = the right-hand side of the operation 884 * 885 * Exceptional_Conditions: 886 * `invalidOperation` is flagged under the following conditions 887 * $(UL 888 * $(LI Adding Infinity and -Infinity, and vice-versa) 889 * $(LI Multiplying +/-Infinity by +/-zero) 890 * $(LI Dividing anything but zero by zero) 891 * $(LI Dividing +/-Infinity by +/-Infinity) 892 * ) 893 * `divisionByZero` is flagged when dividing anything but zero by zero 894 */ 895 auto opBinary(string op, T)(T rhs) const 896 if (op == "+" || op == "-" || op == "*" || op == "/") 897 { 898 auto lhs = dup(); 899 return lhs.opOpAssign!(op, T)(rhs); 900 } 901 902 /// ditto 903 ref Decimal!(Hook) opOpAssign(string op, T)(T rhs) 904 if (op == "+" || op == "-" || op == "*" || op == "/") 905 { 906 static if (isNumeric!T) 907 { 908 auto temp = decimal(rhs); 909 return mixin("this " ~ op ~ "= temp"); 910 } 911 else static if (op == "+" || op == "-") 912 { 913 return addImpl!(op, true)(rhs); 914 } 915 else static if (op == "*") 916 { 917 if (isNan || rhs.isNan) 918 { 919 // the sign of the first nan is simply propagated 920 if (isNan) 921 sign = sign; 922 else if (rhs.isNan) 923 sign = rhs.sign; 924 925 isNan = true; 926 isInf = false; 927 coefficient = 0; 928 exponent = 0; 929 930 return this; 931 } 932 933 sign = sign ^ rhs.sign; 934 935 if (isInf && rhs.isInf) 936 { 937 isInf = true; 938 coefficient = 0; 939 exponent = 0; 940 return this; 941 } 942 943 if (isInf || rhs.isInf) 944 { 945 if ((isInf && rhs.coefficient == 0) || (rhs.isInf && coefficient == 0)) 946 { 947 isNan = true; 948 coefficient = 0; 949 exponent = 0; 950 invalidOperation = true; 951 static if (hasInvalidOperationMethod) 952 hook.onInvalidOperation(this); 953 } 954 else 955 { 956 isInf = true; 957 coefficient = 0; 958 exponent = 0; 959 } 960 961 return this; 962 } 963 964 static if (is(typeof(coefficient) == typeof(rhs.coefficient))) 965 { 966 coefficient = coefficient * rhs.coefficient; 967 } 968 else 969 { 970 import std.conv : to; 971 coefficient = coefficient * to!(typeof(coefficient))(rhs.coefficient); 972 } 973 974 exponent = exponent + rhs.exponent; 975 976 coefficient = round(coefficient); 977 return this; 978 } 979 else static if (op == "/") 980 { 981 if (isNan || rhs.isNan) 982 { 983 // the sign of the first nan is simply propagated 984 if (isNan) 985 sign = sign; 986 else if (rhs.isNan) 987 sign = rhs.sign; 988 989 isInf = false; 990 isNan = true; 991 coefficient = 0; 992 exponent = 0; 993 994 return this; 995 } 996 997 if (isInf && rhs.isInf) 998 { 999 isInf = false; 1000 isNan = true; 1001 coefficient = 0; 1002 exponent = 0; 1003 sign = 0; 1004 invalidOperation = true; 1005 static if (hasInvalidOperationMethod) 1006 hook.onInvalidOperation(this); 1007 return this; 1008 } 1009 1010 if (rhs.coefficient == 0 && coefficient == 0) 1011 { 1012 isNan = true; 1013 coefficient = 0; 1014 exponent = 0; 1015 divisionByZero = true; 1016 1017 static if (hasDivisionByZeroMethod) 1018 hook.onDivisionByZero(this); 1019 1020 return this; 1021 } 1022 1023 sign = sign ^ rhs.sign; 1024 1025 if (isInf && !rhs.isInf) 1026 { 1027 isInf = true; 1028 coefficient = 0; 1029 exponent = 0; 1030 return this; 1031 } 1032 1033 if (!isInf && rhs.isInf) 1034 { 1035 coefficient = 0; 1036 exponent = 0; 1037 return this; 1038 } 1039 1040 if (rhs.coefficient == 0 && coefficient != 0) 1041 { 1042 divisionByZero = true; 1043 invalidOperation = true; 1044 isInf = true; 1045 1046 static if (hasDivisionByZeroMethod) 1047 hook.onDivisionByZero(this); 1048 static if (hasInvalidOperationMethod) 1049 hook.onInvalidOperation(this); 1050 1051 return this; 1052 } 1053 1054 int adjust; 1055 BigInt res; 1056 BigInt dividend = coefficient; 1057 BigInt divisor = rhs.coefficient; 1058 1059 if (dividend !=0) 1060 { 1061 while (dividend < divisor) 1062 { 1063 dividend *= 10; 1064 ++adjust; 1065 } 1066 1067 while (dividend >= divisor * 10) 1068 { 1069 divisor *= 10; 1070 --adjust; 1071 } 1072 1073 while (true) 1074 { 1075 while (divisor <= dividend) 1076 { 1077 dividend -= divisor; 1078 ++res; 1079 } 1080 1081 if ((dividend == 0 && adjust >= 0) || numberOfDigits(res) == precision + 1) 1082 { 1083 break; 1084 } 1085 else 1086 { 1087 res *= 10; 1088 dividend *= 10; 1089 ++adjust; 1090 } 1091 } 1092 } 1093 1094 coefficient = round(res); 1095 exponent = exponent - (rhs.exponent + adjust); 1096 return this; 1097 } 1098 else 1099 { 1100 static assert(0, "Not implemented yet"); 1101 } 1102 } 1103 1104 /** 1105 * Returns: 1106 * `+` simply returns a copy of `this` unchanged. `-` returns a 1107 * copy of `this` with the sign flipped for everything but `0` 1108 * and `NaN`s. 1109 * 1110 * Does not modify the decimal in place. 1111 */ 1112 auto opUnary(string op)() const 1113 if (op == "-" || op == "+") 1114 { 1115 auto res = dup(); 1116 1117 static if (op == "-") 1118 if ((!isNan && coefficient != 0) || isInf) 1119 res.sign = sign == 0 ? 1 : 0; 1120 1121 return res; 1122 } 1123 1124 /** 1125 * Modifies the decimal in place by adding or subtracting 1 for 1126 * `++` and `--` respectively. 1127 */ 1128 ref Decimal!(Hook) opUnary(string op)() 1129 if (op == "++" || op == "--") 1130 { 1131 static immutable one = decimal(1); 1132 static if (op == "++") 1133 this += one; 1134 else 1135 this -= one; 1136 1137 return this; 1138 } 1139 1140 // goes against D's normal nan rules because they're really annoying when 1141 // sorting an array of floating point numbers 1142 /** 1143 * `-Infinity` is less than all numbers, `-NaN` is greater than `-Infinity` but 1144 * less than all other numbers, `NaN` is greater than `-NaN` but less than all other 1145 * numbers and `Infinity` is greater than all numbers. `-NaN` and `NaN` are equal to 1146 * themselves. 1147 * 1148 * Params: 1149 * d = the decimal or built-in number to compare to 1150 * 1151 * Returns: 1152 * Barring special values, `0` if subtracting the two numbers yields 1153 * `0`, `-1` if the result is less than `0`, and `1` if the result is 1154 * greater than zero 1155 */ 1156 int opCmp(T)(T d) const if (isNumeric!T || isInstanceOf!(TemplateOf!(Decimal), T)) 1157 { 1158 static if (!isNumeric!T) 1159 { 1160 if (isInf) 1161 { 1162 if (sign == 1 && (isInf != d.isInf || sign != d.sign)) 1163 return -1; 1164 if (sign == 0 && (isInf != d.isInf || sign != d.sign)) 1165 return 1; 1166 if (d.isInf && sign == d.sign) 1167 return 0; 1168 } 1169 1170 if (isNan && d.isNan) 1171 { 1172 if (sign == d.sign) 1173 return 0; 1174 if (sign == 1) 1175 return -1; 1176 1177 return 1; 1178 } 1179 1180 if (isNan && !d.isNan) 1181 return -1; 1182 if (!isNan && d.isNan) 1183 return 1; 1184 if (sign != d.sign && coefficient != 0 && d.coefficient != 0) 1185 return sign ? -1 : 1; 1186 if (coefficient == d.coefficient && coefficient == 0) 1187 return 0; 1188 1189 auto lhs = dup(); 1190 lhs.addImpl!("-", false)(d); 1191 1192 if (lhs.sign == 0) 1193 { 1194 if (lhs.coefficient == 0) 1195 return 0; 1196 else 1197 return 1; 1198 } 1199 1200 return -1; 1201 } 1202 else 1203 { 1204 static if (isIntegral!T) 1205 { 1206 if (exponent == 0 && sign == 0) 1207 { 1208 if (coefficient == d) 1209 return 0; 1210 if (coefficient < d) 1211 return -1; 1212 if (coefficient > d) 1213 return 1; 1214 } 1215 } 1216 1217 return this.opCmp(d.decimal); 1218 } 1219 } 1220 1221 /// Returns: `true` if `opCmp` would return `0` 1222 bool opEquals(T)(T d) const 1223 { 1224 return this.opCmp(d) == 0; 1225 } 1226 1227 /** 1228 * Throws: 1229 * A `ConvException` if `isIntegral!T` and the decimal is NaN or Infinite. 1230 * 1231 * A `ConvOverflowException` if `isIntegral!T` and the decimal's value 1232 * is outside of `T.min` and `T.max`. 1233 * Returns: 1234 * For `bool`, follows the normal `cast(bool)` rules for floats in D. 1235 * Numbers `<= -1` returns `true`, numbers between `-1` and `1` return 1236 * false, numbers `>= 1` return `true`. 1237 * 1238 * For floating point types, returns a floating point type as close to the 1239 * decimal as possible. 1240 */ 1241 auto opCast(T)() const 1242 if (is(T == bool) || isNumeric!T) 1243 { 1244 static if (is(T == bool)) 1245 { 1246 static immutable negone = decimal(-1); 1247 static immutable one = decimal(1); 1248 1249 if (isNan || isInf) 1250 return true; 1251 1252 if (this <= negone || this >= one) 1253 return true; 1254 1255 return false; 1256 } 1257 else static if (isIntegral!T) 1258 { 1259 import std.algorithm.comparison : max; 1260 import std.conv : to, text, ConvException, ConvOverflowException; 1261 import std.math : abs; 1262 1263 if (isNan || isInf) 1264 throw new ConvException(text( 1265 "Can't cast ", toDecimalString(), " ", Decimal.stringof, " to ", T.stringof 1266 )); 1267 if (this > T.max || this < T.min) 1268 throw new ConvOverflowException(text( 1269 "Can't cast ", toDecimalString(), " ", Decimal.stringof, " to ", T.stringof 1270 )); 1271 1272 T res = to!(T)(coefficient); 1273 1274 if (exponent < 0) 1275 res /= max(10 ^^ abs(exponent), 1); 1276 if (exponent > 0) 1277 res *= max(10 ^^ abs(exponent), 1); 1278 1279 if (sign) 1280 res *= -1; 1281 1282 return res; 1283 } 1284 else 1285 { 1286 import std.conv : to; 1287 1288 if (isInf) 1289 { 1290 if (sign == 0) 1291 return T.infinity; 1292 return -T.infinity; 1293 } 1294 if (isNan) 1295 { 1296 if (sign == 0) 1297 return T.nan; 1298 return -T.nan; 1299 } 1300 1301 // this really needs to be reworked, the problem really 1302 // is that both BigInt and uint128 both cast to ints but 1303 // not to floats, and casting to ints cuts off more than 1304 // half of the number, this method however gets pretty close 1305 // to the equivalent floating point representation of the 1306 // decimal 1307 T res = coefficient.toDecimalString.to!T; 1308 1309 res *= 10.0 ^^ exponent; 1310 if (sign == 1) 1311 res *= -1; 1312 return res; 1313 } 1314 } 1315 1316 /** 1317 * Convenience function to reset all exceptional condition flags to `false` at once 1318 */ 1319 void resetFlags() @safe @nogc pure nothrow 1320 { 1321 clamped = false; 1322 divisionByZero = false; 1323 inexact = false; 1324 invalidOperation = false; 1325 overflow = false; 1326 rounded = false; 1327 subnormal = false; 1328 underflow = false; 1329 } 1330 1331 /// Returns: A mutable copy of this `Decimal`. Also copies current flags. 1332 Decimal!(Hook) dup()() const 1333 { 1334 Unqual!(typeof(this)) res; 1335 res.coefficient = coefficient; 1336 res.exponent = exponent; 1337 res.sign = sign; 1338 res.isNan = isNan; 1339 res.isInf = isInf; 1340 res.clamped = clamped; 1341 res.divisionByZero = divisionByZero; 1342 res.inexact = inexact; 1343 res.invalidOperation = invalidOperation; 1344 res.overflow = overflow; 1345 res.rounded = rounded; 1346 res.subnormal = subnormal; 1347 res.underflow = underflow; 1348 1349 return res; 1350 } 1351 1352 /// Returns: An immutable copy of this `Decimal`. Also copies current flags. 1353 immutable(Decimal!(Hook)) idup()() const 1354 { 1355 return dup!()(); 1356 } 1357 1358 /// Returns: A decimal representing a positive NaN 1359 static Decimal!(Hook) nan()() @property 1360 { 1361 Decimal!(Hook) res; 1362 res.isNan = true; 1363 return res; 1364 } 1365 1366 /// Returns: A decimal representing positive Infinity 1367 static Decimal!(Hook) infinity()() @property 1368 { 1369 Decimal!(Hook) res; 1370 res.isInf = true; 1371 return res; 1372 } 1373 1374 /** 1375 * Returns: The maximum value that this decimal type can represent. 1376 * Equal to `(1 * 10 ^^ (maxExponent + 1)) - 1` 1377 */ 1378 static Decimal!(Hook) max()() @property 1379 { 1380 import std.range : repeat; 1381 Decimal!(Hook) res; 1382 res.coefficient = BigInt('9'.repeat(precision)); 1383 res.exponent = maxExponent; 1384 return res; 1385 } 1386 1387 /** 1388 * Returns: The minimum value that this decimal type can represent. 1389 * Equal to `-1 * 10 ^^ minExponent` 1390 */ 1391 static Decimal!(Hook) min()() @property 1392 { 1393 Decimal!(Hook) res; 1394 res.coefficient = 1; 1395 res.exponent = minExponent; 1396 res.sign = 1; 1397 return res; 1398 } 1399 1400 /// Exceptional condition flags 1401 bool sign() @safe pure nothrow @nogc const @property { return (signAndFlags & 1U) != 0; } 1402 /// ditto 1403 bool isNan() @safe pure nothrow @nogc const @property { return (signAndFlags & 2U) != 0;} 1404 /// ditto 1405 bool isInf() @safe pure nothrow @nogc const @property { return (signAndFlags & 4U) != 0;} 1406 /// ditto 1407 bool clamped() @safe pure nothrow @nogc const @property { return (signAndFlags & 8U) != 0;} 1408 /// ditto 1409 bool divisionByZero() @safe pure nothrow @nogc const @property { return (signAndFlags & 16U) != 0;} 1410 /// ditto 1411 bool inexact() @safe pure nothrow @nogc const @property { return (signAndFlags & 32U) != 0;} 1412 /// ditto 1413 bool invalidOperation() @safe pure nothrow @nogc const @property { return (signAndFlags & 64U) != 0;} 1414 /// ditto 1415 bool overflow() @safe pure nothrow @nogc const @property { return (signAndFlags & 128U) != 0;} 1416 /// ditto 1417 bool rounded() @safe pure nothrow @nogc const @property { return (signAndFlags & 256U) != 0;} 1418 /// ditto 1419 bool subnormal() @safe pure nothrow @nogc const @property { return (signAndFlags & 512U) != 0;} 1420 /// ditto 1421 bool underflow() @safe pure nothrow @nogc const @property { return (signAndFlags & 1024U) != 0;} 1422 1423 /// 1424 alias toString = toDecimalString; 1425 1426 /// Returns: Returns the decimal string representation of this decimal. 1427 auto toDecimalString() const 1428 { 1429 import std.array : appender; 1430 import std.math : abs; 1431 1432 auto app = appender!string(); 1433 if (exponent > 10 || exponent < -10) 1434 app.reserve(abs(exponent) + precision); 1435 toDecimalString(app); 1436 return app.data; 1437 } 1438 1439 /// ditto 1440 void toDecimalString(Writer)(ref Writer w) const if (isOutputRange!(Writer, char)) 1441 { 1442 import std.math : pow; 1443 import std.range : repeat; 1444 1445 if (sign == 1) 1446 put(w, '-'); 1447 1448 if (isInf) 1449 { 1450 put(w, "Infinity"); 1451 return; 1452 } 1453 1454 if (isNan) 1455 { 1456 put(w, "NaN"); 1457 return; 1458 } 1459 1460 auto temp = coefficient.toDecimalString; 1461 auto decimalPlace = exponent * -1; 1462 1463 if (decimalPlace > 0) 1464 { 1465 if (temp.length - decimalPlace == 0) 1466 { 1467 put(w, "0."); 1468 put(w, temp); 1469 return; 1470 } 1471 1472 if ((cast(long) temp.length) - decimalPlace > 0) 1473 { 1474 put(w, temp[0 .. $ - decimalPlace]); 1475 put(w, '.'); 1476 put(w, temp[$ - decimalPlace .. $]); 1477 return; 1478 } 1479 1480 if ((cast(long) temp.length) - decimalPlace < 0) 1481 { 1482 put(w, "0."); 1483 put(w, '0'.repeat(decimalPlace - temp.length)); 1484 put(w, temp); 1485 return; 1486 } 1487 } 1488 1489 if (decimalPlace < 0) 1490 { 1491 put(w, temp); 1492 put(w, '0'.repeat(exponent)); 1493 return; 1494 } 1495 1496 put(w, temp); 1497 } 1498 } 1499 1500 // string construction 1501 @system 1502 unittest 1503 { 1504 static struct Test 1505 { 1506 string val; 1507 ubyte sign; 1508 int coefficient; 1509 long exponent; 1510 } 1511 1512 static struct SpecialTest 1513 { 1514 string val; 1515 ubyte sign; 1516 bool isNan; 1517 bool isInf; 1518 bool invalid; 1519 } 1520 1521 auto nonspecialTestValues = [ 1522 Test("0", 0, 0, 0), 1523 Test("+0", 0, 0, 0), 1524 Test("-0", 1, 0, 0), 1525 Test("1.0", 0, 10, -1), 1526 Test("0E+7", 0, 0, 7), 1527 Test("-0E-7", 1, 0, -7), 1528 Test("1.23E3", 0, 123, 1), 1529 Test("0001.0000", 0, 10_000, -4), 1530 Test("-10.0004", 1, 100_004, -4), 1531 Test("+15", 0, 15, 0), 1532 Test("-15", 1, 15, 0), 1533 Test("1234.5E-4", 0, 12_345, -5), 1534 Test("30.5E10", 0, 305, 9) 1535 ]; 1536 1537 auto specialTestValues = [ 1538 SpecialTest("NaN", 0, true, false, false), 1539 SpecialTest("+nan", 0, true, false, false), 1540 SpecialTest("-nan", 1, true, false, false), 1541 SpecialTest("-NAN", 1, true, false, false), 1542 SpecialTest("Infinite", 0, true, false, true), 1543 SpecialTest("infinity", 0, false, true, false), 1544 SpecialTest("-INFINITY", 1, false, true, false), 1545 SpecialTest("inf", 0, false, true, false), 1546 SpecialTest("-inf", 1, false, true, false), 1547 SpecialTest("Jack", 0, true, false, true), 1548 SpecialTest("+", 0, true, false, true), 1549 SpecialTest("-", 0, true, false, true), 1550 SpecialTest("nan0123", 0, true, false, false), 1551 SpecialTest("-nan0123", 1, true, false, false), 1552 SpecialTest("12+3", 0, true, false, true), 1553 SpecialTest("1.2.3", 0, true, false, true), 1554 SpecialTest("123.0E+7E+7", 0, true, false, true), 1555 ]; 1556 1557 foreach (el; nonspecialTestValues) 1558 { 1559 auto d = Decimal!()(el.val); 1560 assert(d.coefficient == el.coefficient); 1561 assert(d.sign == el.sign); 1562 assert(d.exponent == el.exponent); 1563 } 1564 1565 foreach (el; specialTestValues) 1566 { 1567 auto d = Decimal!(NoOp)(el.val); 1568 assert(d.isNan == el.isNan); 1569 assert(d.isInf == el.isInf); 1570 assert(d.invalidOperation == el.invalid); 1571 } 1572 } 1573 1574 // range construction 1575 @system pure 1576 unittest 1577 { 1578 import std.internal.test.dummyrange; 1579 auto r1 = new ReferenceForwardRange!dchar("123.456"); 1580 auto d1 = Decimal!()(r1); 1581 assert(d1.coefficient == 123_456); 1582 assert(d1.sign == 0); 1583 assert(d1.exponent == -3); 1584 1585 auto r2 = new ReferenceForwardRange!dchar("-0.00004"); 1586 auto d2 = Decimal!()(r2); 1587 assert(d2.coefficient == 4); 1588 assert(d2.sign == 1); 1589 assert(d2.exponent == -5); 1590 } 1591 1592 // int construction 1593 @system 1594 unittest 1595 { 1596 static struct Test 1597 { 1598 long val; 1599 ubyte sign; 1600 long coefficient; 1601 } 1602 1603 static immutable testValues = [ 1604 Test(10, 0, 10), 1605 Test(-10, 1, 10), 1606 Test(-1_000_000, 1, 1_000_000), 1607 Test(-147_483_648, 1, 147_483_648), 1608 ]; 1609 1610 foreach (el; testValues) 1611 { 1612 auto d = Decimal!()(el.val); 1613 assert(d.coefficient == el.coefficient); 1614 assert(d.sign == el.sign); 1615 } 1616 } 1617 1618 // float construction 1619 @system 1620 unittest 1621 { 1622 static struct Test 1623 { 1624 double val; 1625 ubyte sign; 1626 long coefficient; 1627 long exponent; 1628 } 1629 1630 static struct SpecialTest 1631 { 1632 double val; 1633 ubyte sign; 1634 bool isNan; 1635 bool isInf; 1636 } 1637 1638 auto nonspecialTestValues = [ 1639 Test(0.02, 0, 2, -2), 1640 Test(0.00002, 0, 2, -5), 1641 Test(1.02, 0, 102, -2), 1642 Test(200.0, 0, 200, 0), 1643 Test(1234.5678, 0, 12_345_678, -4), 1644 Test(-1234.5678, 1, 12_345_678, -4), 1645 Test(-1234, 1, 1234, 0), 1646 Test(int.min, 1, 2147483648, 0), 1647 ]; 1648 1649 auto specialTestValues = [ 1650 SpecialTest(float.nan, 0, true, false), 1651 SpecialTest(-float.nan, 1, true, false), 1652 SpecialTest(float.infinity, 0, false, true), 1653 SpecialTest(-float.infinity, 1, false, true), 1654 ]; 1655 1656 foreach (el; nonspecialTestValues) 1657 { 1658 auto d = Decimal!()(el.val); 1659 assert(d.coefficient == el.coefficient); 1660 assert(d.sign == el.sign); 1661 assert(d.exponent == el.exponent); 1662 1663 Decimal!() d2; 1664 d2 = el.val; 1665 assert(d2.coefficient == el.coefficient); 1666 assert(d2.sign == el.sign); 1667 assert(d2.exponent == el.exponent); 1668 } 1669 1670 foreach (el; specialTestValues) 1671 { 1672 auto d = Decimal!()(el.val); 1673 assert(d.sign == el.sign); 1674 assert(d.isNan == el.isNan); 1675 assert(d.isInf == el.isInf); 1676 1677 Decimal!() d2; 1678 d2 = el.val; 1679 assert(d2.sign == el.sign); 1680 assert(d2.isNan == el.isNan); 1681 assert(d2.isInf == el.isInf); 1682 } 1683 } 1684 1685 // static ctors 1686 @system pure 1687 unittest 1688 { 1689 alias DType = Decimal!(); 1690 1691 auto d1 = DType.nan; 1692 assert(d1.isNan == true); 1693 auto d2 = DType.infinity; 1694 assert(d2.isInf == true); 1695 1696 auto d3 = DType.max; 1697 assert(d3 == decimal("9999999999999999E999")); 1698 auto d4 = DType.min; 1699 assert(d4 == decimal("-1E-999")); 1700 } 1701 1702 // addition and subtraction 1703 @system 1704 unittest 1705 { 1706 static struct Test 1707 { 1708 string val1; 1709 string val2; 1710 string expected; 1711 bool invalidOperation; 1712 } 1713 1714 auto testPlusValues = [ 1715 Test("-0", "-0", "-0"), 1716 Test("-0", "0", "0"), 1717 Test("1", "2", "3"), 1718 Test("-5", "-3", "-8"), 1719 Test("5.75", "3.3", "9.05"), 1720 Test("1.23456789", "1.00000000", "2.23456789"), 1721 Test("10E5", "10E4", "1100000"), 1722 Test("10E25", "10E4", "100000000000000000000000000"), 1723 Test("0.9998", "0.0000", "0.9998"), 1724 Test("1", "0.0001", "1.0001"), 1725 Test("1", "0.00", "1.00"), 1726 Test("123.00", "3000.00", "3123.00"), 1727 Test("123.00", "3000.00", "3123.00"), 1728 Test("1000", "NaN", "NaN"), 1729 Test("NaN", "NaN", "NaN"), 1730 Test("-NaN", "NaN", "-NaN"), 1731 Test("NaN", "Inf", "NaN"), 1732 Test("-NaN", "Inf", "-NaN"), 1733 Test("-Inf", "-Inf", "-Infinity"), 1734 Test("-Inf", "Inf", "NaN", true), 1735 Test("Inf", "-Inf", "NaN", true), 1736 Test("-Inf", "-1000", "-Infinity"), 1737 Test("-Inf", "1000", "-Infinity"), 1738 Test("Inf", "-1000", "Infinity"), 1739 Test("Inf", "1000", "Infinity") 1740 ]; 1741 1742 auto testMinusValues = [ 1743 Test("-0", "0", "-0"), 1744 Test("0", "-0", "0"), 1745 Test("0", "0", "0"), 1746 Test("1.3", "0.3", "1.0"), 1747 Test("1.3", "2.07", "-0.77"), 1748 Test("1.25", "1.25", "0.00"), 1749 Test("3", "-3.0", "6.0"), 1750 Test("1.23456789", "1.00000000", "0.23456789"), 1751 Test("10.2345679", "10.2345675", "0.0000004"), 1752 Test("0.999999999", "1", "-0.000000001"), 1753 Test("2.000E-3", "1.00200", "-1.000000"), 1754 Test("-Inf", "Inf", "-Infinity"), 1755 Test("-Inf", "1000", "-Infinity"), 1756 Test("1000", "-Inf", "Infinity"), 1757 Test("NaN", "Inf", "NaN"), 1758 Test("Inf", "NaN", "NaN"), 1759 Test("NaN", "NaN", "NaN"), 1760 Test("-NaN", "NaN", "-NaN"), 1761 Test("Inf", "Inf", "NaN", true), 1762 Test("-Inf", "-Inf", "NaN", true), 1763 ]; 1764 1765 foreach (el; testPlusValues) 1766 { 1767 auto v1 = decimal!(NoOp)(el.val1); 1768 auto v2 = decimal(el.val2); 1769 auto res = v1 + v2; 1770 assert(res.toString() == el.expected); 1771 assert(res.invalidOperation == el.invalidOperation); 1772 } 1773 1774 foreach (el; testMinusValues) 1775 { 1776 auto v1 = decimal!(NoOp)(el.val1); 1777 auto v2 = decimal(el.val2); 1778 auto res = v1 - v2; 1779 assert(res.toString() == el.expected); 1780 assert(res.invalidOperation == el.invalidOperation); 1781 } 1782 1783 // check that float and int compile 1784 assert(decimal("2.22") + 0.01 == decimal("2.23")); 1785 assert(decimal("2.22") + 1 == decimal("3.22")); 1786 1787 // test that opOpAssign works properly 1788 auto d1 = decimal("3.55"); 1789 d1 += 0.45; 1790 assert(d1.toString() == "4.00"); 1791 auto d2 = decimal("3.55"); 1792 d2 -= 0.55; 1793 assert(d2.toString() == "3.00"); 1794 1795 static struct CustomHook 1796 { 1797 enum Rounding roundingMode = Rounding.HalfUp; 1798 enum uint precision = 3; 1799 } 1800 1801 // rounding test on addition 1802 auto d3 = decimal!(CustomHook)("0.999E-2"); 1803 auto d4 = decimal!(CustomHook)("0.1E-2"); 1804 auto v = d3 + d4; 1805 assert(v.toString == "0.0110"); 1806 assert(v.inexact); 1807 assert(v.rounded); 1808 1809 // higher precision tests 1810 auto d5 = decimal!(HighPrecision)("10000e+9"); 1811 auto d6 = decimal!(HighPrecision)("7"); 1812 auto v2 = d5 - d6; 1813 assert(v2.toString() == "9999999999993"); 1814 1815 auto d7 = decimal!(HighPrecision)("1e-50"); 1816 auto d8 = decimal!(HighPrecision)("4e-50"); 1817 auto v3 = d7 + d8; 1818 assert(v3.toString() == "0.00000000000000000000000000000000000000000000000005"); 1819 } 1820 1821 // multiplication 1822 @system 1823 unittest 1824 { 1825 static struct Test 1826 { 1827 string val1; 1828 string val2; 1829 string expected; 1830 bool invalidOperation; 1831 bool rounded; 1832 } 1833 1834 auto testValues = [ 1835 Test("0", "0", "0"), 1836 Test("0", "-0", "-0"), 1837 Test("-0", "0", "-0"), 1838 Test("-0", "-0", "0"), 1839 Test("-00.00", "0E-3", "-0.00000"), 1840 Test("1.20", "3", "3.60"), 1841 Test("7", "3", "21"), 1842 Test("0.9", "0.8", "0.72"), 1843 Test("0.9", "-0", "-0.0"), 1844 Test("-1.20", "-2", "2.40"), 1845 Test("123.45", "1e7", "1234500000"), 1846 Test("12345", "10E-3", "123.450"), 1847 Test("1.23456789", "1.00000000", "1.234567890000000", false, true), 1848 Test("123456789", "10", "1234567890", false, false), 1849 Test("123456789", "100000000000000", "12345678900000000000000", false, true), 1850 Test("Inf", "-Inf", "-Infinity"), 1851 Test("-1000", "Inf", "-Infinity"), 1852 Test("Inf", "1000", "Infinity"), 1853 Test("0", "Inf", "NaN", true), 1854 Test("-Inf", "-Inf", "Infinity"), 1855 Test("Inf", "Inf", "Infinity"), 1856 Test("NaN", "Inf", "NaN"), 1857 Test("NaN", "-1000", "NaN"), 1858 Test("-NaN", "-1000", "-NaN"), 1859 Test("-NaN", "-NaN", "-NaN"), 1860 Test("-NaN", "-Inf", "-NaN"), 1861 Test("Inf", "-NaN", "-NaN") 1862 ]; 1863 1864 foreach (el; testValues) 1865 { 1866 auto d = decimal!(NoOp)(el.val1) * decimal!(NoOp)(el.val2); 1867 assert(d.toString() == el.expected); 1868 assert(d.invalidOperation == el.invalidOperation); 1869 assert(d.rounded == el.rounded); 1870 } 1871 1872 // test that opOpAssign works properly 1873 auto d1 = decimal("2.5"); 1874 d1 *= 5.4; 1875 assert(d1.toString() == "13.50"); 1876 } 1877 1878 // division 1879 @system 1880 unittest 1881 { 1882 import std.exception : assertThrown; 1883 1884 static struct Test 1885 { 1886 string val1; 1887 string val2; 1888 string expected; 1889 bool divisionByZero; 1890 bool invalidOperation; 1891 bool inexact; 1892 bool rounded; 1893 } 1894 1895 auto testValues = [ 1896 Test("5", "2", "2.5"), 1897 Test("1", "10", "0.1"), 1898 Test("1", "4", "0.25"), 1899 Test("12", "12", "1"), 1900 Test("8.00", "2", "4.00"), 1901 Test("1000", "100", "10"), 1902 Test("2.40E+6", "2", "1200000"), 1903 Test("2.4", "-1", "-2.4"), 1904 Test("0.0", "1", "0.0"), 1905 Test("0.0", "-1", "-0.0"), 1906 Test("-0.0", "-1", "0.0"), 1907 Test("1", "3", "0.3333333333333333", false, false, true, true), 1908 Test("2", "3", "0.6666666666666667", false, false, true, true), 1909 Test("0", "0", "NaN", true, false), 1910 Test("1000", "0", "Infinity", true, true), 1911 Test("-1000", "0", "-Infinity", true, true), 1912 Test("Inf", "-Inf", "NaN", false, true), 1913 Test("-Inf", "Inf", "NaN", false, true), 1914 Test("Inf", "1000", "Infinity"), 1915 Test("Inf", "-1000", "-Infinity"), 1916 Test("1000", "Inf", "0"), 1917 Test("1000", "-Inf", "-0"), 1918 Test("Inf", "Inf", "NaN", false, true), 1919 Test("-Inf", "-Inf", "NaN", false, true), 1920 Test("NaN", "NaN", "NaN"), 1921 Test("-NaN", "NaN", "-NaN"), 1922 Test("NaN", "-Inf", "NaN"), 1923 Test("-NaN", "Inf", "-NaN"), 1924 Test("NaN", "-1000", "NaN"), 1925 Test("Inf", "NaN", "NaN"), 1926 ]; 1927 1928 foreach (el; testValues) 1929 { 1930 auto d = decimal!(NoOp)(el.val1) / decimal!(NoOp)(el.val2); 1931 assert(d.toString() == el.expected); 1932 assert(d.divisionByZero == el.divisionByZero); 1933 assert(d.invalidOperation == el.invalidOperation); 1934 assert(d.inexact == el.inexact); 1935 assert(d.rounded == el.rounded); 1936 } 1937 1938 // test that opOpAssign works properly 1939 auto d1 = decimal(1000); 1940 d1 /= 10; 1941 assert(d1.toString() == "100"); 1942 1943 // test that the proper DivisionByZero function is called 1944 assertThrown!DivisionByZero(() { cast(void) (decimal!(Throw)(1) / decimal(0)); } ()); 1945 } 1946 1947 // cmp and equals 1948 @system 1949 unittest 1950 { 1951 static struct Test 1952 { 1953 string val1; 1954 string val2; 1955 int expected; 1956 bool invalidOperation; 1957 } 1958 1959 auto testValues = [ 1960 Test("inf", "0", 1), 1961 Test("-inf", "0", -1), 1962 Test("-inf", "-inf", 0), 1963 Test("inf", "-inf", 1), 1964 Test("-inf", "inf", -1), 1965 Test("NaN", "1000", -1), 1966 Test("-NaN", "1000", -1), 1967 Test("1000", "NAN", 1), 1968 Test("1000", "-NAN", 1), 1969 Test("NaN", "inf", -1), 1970 Test("-NaN", "inf", -1), 1971 Test("-NaN", "NaN", -1), 1972 Test("NaN", "-NaN", 1), 1973 Test("-NaN", "-NaN", 0), 1974 Test("NaN", "NaN", 0), 1975 Test("0", "-0", 0), 1976 Test("2.1", "3", -1), 1977 Test("2.1", "2.1", 0), 1978 Test("2.1", "2.10", 0), 1979 Test("3", "2.1", 1), 1980 Test("2.1", "-3", 1), 1981 Test("-3", "2.1", -1), 1982 Test("00", "00", 0), 1983 Test("70E-1", "7", 0), 1984 Test("8", "0.7E+1", 1), 1985 Test("-8.0", "7.0", -1), 1986 Test("80E-1", "-9", 1), 1987 Test("1E-15", "1", -1), 1988 Test("-0E2", "0", 0), 1989 Test("-8", "-70E-1", -1), 1990 Test("-12.1234", "-12.000000000", -1), 1991 ]; 1992 1993 foreach (el; testValues) 1994 { 1995 auto v1 = decimal!(NoOp)(el.val1); 1996 auto v2 = decimal!(NoOp)(el.val2); 1997 assert(v1.opCmp(v2) == el.expected); 1998 assert(v1.invalidOperation == el.invalidOperation); 1999 } 2000 2001 // make sure equals compiles, already covered behavior in 2002 // cmp tests 2003 assert(decimal("19.9999") != decimal("21.222222")); 2004 assert(decimal("22.000") == decimal("22")); 2005 assert(decimal("22.000") == 22); 2006 assert(decimal("22.2") == 22.2); 2007 } 2008 2009 // unary 2010 @system 2011 unittest 2012 { 2013 auto testPlusValues = [ 2014 ["1", "1"], 2015 ["0", "0"], 2016 ["00.00", "0.00"], 2017 ["-2000000", "-2000000"], 2018 ["Inf", "Infinity"], 2019 ["NaN", "NaN"], 2020 ["-NaN", "-NaN"], 2021 ]; 2022 2023 auto testMinusValues = [ 2024 ["1", "-1"], 2025 ["-1", "1"], 2026 ["0.00", "0.00"], 2027 ["-2000000", "2000000"], 2028 ["Inf", "-Infinity"], 2029 ["NaN", "NaN"], 2030 ["-NaN", "-NaN"] 2031 ]; 2032 2033 auto testPlusPlusValues = [ 2034 ["1", "2"], 2035 ["-1", "0"], 2036 ["0.00", "1.00"], 2037 ["1.0000001", "2.0000001"], 2038 ["-2000000", "-1999999"], 2039 ["Inf", "Infinity"], 2040 ["-Inf", "-Infinity"], 2041 ["NaN", "NaN"], 2042 ["-NaN", "-NaN"] 2043 ]; 2044 2045 auto testMinusMinusValues = [ 2046 ["1", "0"], 2047 ["-1", "-2"], 2048 ["1.00", "0.00"], 2049 ["1.0000001", "0.0000001"], 2050 ["-2000000", "-2000001"], 2051 ["Inf", "Infinity"], 2052 ["-Inf", "-Infinity"], 2053 ["NaN", "NaN"], 2054 ["-NaN", "-NaN"] 2055 ]; 2056 2057 foreach (el; testPlusValues) 2058 { 2059 auto d1 = decimal!(NoOp)(el[0]); 2060 auto d2 = +d1; 2061 assert(d2.toString == el[1]); 2062 } 2063 foreach (el; testMinusValues) 2064 { 2065 auto d1 = decimal!(NoOp)(el[0]); 2066 auto d2 = -d1; 2067 assert(d2.toString == el[1]); 2068 } 2069 foreach (el; testPlusPlusValues) 2070 { 2071 auto d1 = decimal!(NoOp)(el[0]); 2072 ++d1; 2073 assert(d1.toString == el[1]); 2074 } 2075 foreach (el; testMinusMinusValues) 2076 { 2077 auto d1 = decimal!(NoOp)(el[0]); 2078 --d1; 2079 assert(d1.toString == el[1]); 2080 } 2081 } 2082 2083 // opCast 2084 @system 2085 unittest 2086 { 2087 import std.exception : assertThrown; 2088 import std.conv : ConvException, ConvOverflowException; 2089 import std.math : approxEqual, isNaN; 2090 2091 assert((cast(bool) decimal("0.0")) == false); 2092 assert((cast(bool) decimal("0.5")) == false); 2093 assert((cast(bool) decimal("-0.5")) == false); 2094 assert((cast(bool) decimal("-1.0")) == true); 2095 assert((cast(bool) decimal("1.0")) == true); 2096 assert((cast(bool) decimal("1.1")) == true); 2097 assert((cast(bool) decimal("-1.1")) == true); 2098 assert((cast(bool) decimal("Infinity")) == true); 2099 assert((cast(bool) decimal("-Infinity")) == true); 2100 assert((cast(bool) decimal("-NaN")) == true); 2101 assert((cast(bool) decimal("NaN")) == true); 2102 2103 assert((cast(real) decimal("0.0")).approxEqual(0)); 2104 assert((cast(real) decimal("0.0123")).approxEqual(0.0123)); 2105 assert((cast(real) decimal("123")).approxEqual(123.0)); 2106 assert((cast(real) decimal("10.8888")).approxEqual(10.8888)); 2107 assert(isNaN((cast(real) decimal("NaN")))); 2108 assert(isNaN((cast(real) decimal("-NaN")))); 2109 assert((cast(real) decimal("Inf")) == real.infinity); 2110 assert((cast(real) decimal("-Inf")) == -real.infinity); 2111 2112 assert((cast(int) decimal("1")) == 1); 2113 assert((cast(int) decimal("1.0")) == 1); 2114 assert((cast(int) decimal("0.0")) == 0); 2115 assert((cast(int) decimal("-0")) == 0); 2116 assert((cast(int) decimal("-1")) == -1); 2117 assert((cast(int) decimal("0.0001")) == 0); 2118 assert((cast(int) decimal("10000.0001")) == 10000); 2119 assert((cast(ulong) decimal("-0")) == 0); 2120 assert((cast(ulong) decimal("0.0001")) == 0); 2121 assert((cast(ulong) decimal("10000.0001")) == 10000); 2122 2123 assertThrown!(ConvOverflowException)((cast(ulong) decimal("-1"))); 2124 assertThrown!(ConvException)((cast(ulong) decimal("INF"))); 2125 assertThrown!(ConvException)((cast(ulong) decimal("NaN"))); 2126 2127 static struct CustomHook 2128 { 2129 enum Rounding roundingMode = Rounding.HalfUp; 2130 enum uint precision = 19; 2131 } 2132 2133 assert(( 2134 cast(real) decimal!(CustomHook)("12345678910111.213") 2135 ).approxEqual(12345678910111.213)); 2136 2137 assert(( 2138 cast(real) decimal!(HighPrecision)("12345678910111213141516.1718192021") 2139 ).approxEqual(12345678910111213141516.1718192021)); 2140 } 2141 2142 // to string 2143 @system 2144 unittest 2145 { 2146 auto t = Decimal!()(); 2147 t.sign = 0; 2148 t.coefficient = 2_708; 2149 t.exponent = -2; 2150 assert(t.toString() == "27.08"); 2151 2152 auto t2 = Decimal!()(); 2153 t2.sign = 1; 2154 t2.coefficient = 1_953; 2155 t2.exponent = 0; 2156 assert(t2.toString() == "-1953"); 2157 2158 auto t3 = Decimal!()(); 2159 t3.sign = 0; 2160 t3.coefficient = 9_888_555_555; 2161 t3.exponent = -4; 2162 assert(t3.toString() == "988855.5555"); 2163 2164 auto t4 = Decimal!()("300088.44"); 2165 assert(t4.toString() == "300088.44"); 2166 2167 auto t5 = Decimal!()("30.5E10"); 2168 assert(t5.toString() == "305000000000"); 2169 2170 auto t6 = Decimal!()(10); 2171 assert(t6.toString() == "10"); 2172 2173 auto t7 = Decimal!()(12_345_678); 2174 assert(t7.toString() == "12345678"); 2175 2176 auto t8 = Decimal!()(1234.5678); 2177 assert(t8.toString() == "1234.5678"); 2178 2179 auto t9 = Decimal!()(0.1234); 2180 assert(t9.toString() == "0.1234"); 2181 2182 auto t10 = Decimal!()(1234.0); 2183 assert(t10.toString() == "1234"); 2184 2185 auto t11 = Decimal!()("1.2345678E-7"); 2186 assert(t11.toString() == "0.00000012345678"); 2187 2188 auto t12 = Decimal!()("INF"); 2189 assert(t12.toString() == "Infinity"); 2190 2191 auto t13 = Decimal!()("-INF"); 2192 assert(t13.toString() == "-Infinity"); 2193 2194 auto t14 = Decimal!()("NAN"); 2195 assert(t14.toString() == "NaN"); 2196 2197 auto t15 = Decimal!()("-NAN"); 2198 assert(t15.toString() == "-NaN"); 2199 } 2200 2201 // test hook setting enums 2202 @safe pure 2203 unittest 2204 { 2205 static assert(Decimal!(void).precision == 16); 2206 static assert(Decimal!(void).roundingMode == Rounding.HalfUp); 2207 static assert(Decimal!(void).maxExponent == 999); 2208 static assert(Decimal!(void).minExponent == -999); 2209 2210 static struct A 2211 { 2212 enum maxExponent = 10; 2213 } 2214 static assert(Decimal!(A).precision == 16); 2215 static assert(Decimal!(A).roundingMode == Rounding.HalfUp); 2216 static assert(Decimal!(A).maxExponent == 10); 2217 static assert(Decimal!(A).minExponent == -999); 2218 2219 static struct B 2220 { 2221 enum precision = 10; 2222 enum Rounding roundingMode = Rounding.Down; 2223 enum maxExponent = 10; 2224 enum minExponent = -10; 2225 } 2226 static assert(Decimal!(B).precision == 10); 2227 static assert(Decimal!(B).roundingMode == Rounding.Down); 2228 static assert(Decimal!(B).maxExponent == 10); 2229 static assert(Decimal!(B).minExponent == -10); 2230 } 2231 2232 // test rounding 2233 @system 2234 unittest 2235 { 2236 import std.exception : assertThrown; 2237 2238 static struct Test 2239 { 2240 ulong coefficient; 2241 ulong expected; 2242 bool inexact; 2243 bool rounded; 2244 } 2245 2246 static struct DownHook 2247 { 2248 enum uint precision = 5; 2249 enum Rounding roundingMode = Rounding.Down; 2250 } 2251 2252 static struct UpHook 2253 { 2254 enum uint precision = 5; 2255 enum Rounding roundingMode = Rounding.Up; 2256 } 2257 2258 static struct HalfUpHook 2259 { 2260 enum uint precision = 5; 2261 enum Rounding roundingMode = Rounding.HalfUp; 2262 } 2263 2264 auto downValues = [ 2265 Test(12_345, 12345, false, false), 2266 Test(123_449, 12344, true, true), 2267 Test(1_234_499_999, 12_344, true, true), 2268 Test(123_451, 12_345, true, true), 2269 Test(123_450_000_001, 12_345, true, true), 2270 Test(1_234_649_999, 12_346, true, true), 2271 Test(123_465, 12_346, true, true), 2272 Test(1_234_650_001, 12_346, true, true), 2273 Test(1_234_500, 12_345, false, true) 2274 ]; 2275 auto upValues = [ 2276 Test(12_345, 12_345, false, false), 2277 Test(1_234_499, 12_345, true, true), 2278 Test(123_449_999_999, 12_345, true, true), 2279 Test(123_450_000_001, 12_346, true, true), 2280 Test(123_451, 12_346, true, true), 2281 Test(1_234_649_999, 12_347, true, true), 2282 Test(123_465, 12_347, true, true), 2283 Test(123_454, 12_346, true, true), 2284 Test(1_234_500, 12_345, false, true) 2285 ]; 2286 auto halfUpValues = [ 2287 Test(12_345, 12_345, false, false), 2288 Test(123_449, 12_345, true, true), 2289 Test(1_234_499, 12_345, true, true), 2290 Test(12_344_999, 12_345, true, true), 2291 Test(123_451, 12_345, true, true), 2292 Test(1_234_501, 12_345, true, true), 2293 Test(123_464_999, 12_346, true, true), 2294 Test(123_465, 12_347, true, true), 2295 Test(1_234_650_001, 12_347, true, true), 2296 Test(123_456, 12_346, true, true), 2297 Test(1_234_500, 12_345, false, true) 2298 ]; 2299 2300 foreach (e; downValues) 2301 { 2302 auto d = Decimal!(DownHook)(e.coefficient); 2303 assert(d.coefficient == e.expected); 2304 assert(d.rounded == e.rounded); 2305 assert(d.inexact == e.inexact); 2306 } 2307 foreach (e; upValues) 2308 { 2309 auto d = Decimal!(UpHook)(e.coefficient); 2310 assert(d.coefficient == e.expected); 2311 assert(d.rounded == e.rounded); 2312 assert(d.inexact == e.inexact); 2313 } 2314 foreach (e; halfUpValues) 2315 { 2316 auto d = Decimal!(HalfUpHook)(e.coefficient); 2317 assert(d.coefficient == e.expected); 2318 assert(d.rounded == e.rounded); 2319 assert(d.inexact == e.inexact); 2320 } 2321 2322 // Test that the exponent is properly changed 2323 auto d1 = decimal!(HalfUpHook)("1.2345678E-7"); 2324 assert(d1.exponent == -11); 2325 2326 // test calling of defined hook methods 2327 static struct ThrowHook 2328 { 2329 enum uint precision = 5; 2330 enum Rounding roundingMode = Rounding.HalfUp; 2331 2332 static void onRounded(T)(T d) 2333 { 2334 throw new Exception("Rounded"); 2335 } 2336 } 2337 2338 assertThrown!Exception(Decimal!(ThrowHook)(1_234_567)); 2339 2340 static struct HigherHook 2341 { 2342 enum uint precision = 16; 2343 enum Rounding roundingMode = Rounding.HalfUp; 2344 } 2345 2346 static struct HigherHookDown 2347 { 2348 enum uint precision = 16; 2349 enum Rounding roundingMode = Rounding.Down; 2350 } 2351 2352 static struct HigherHookUp 2353 { 2354 enum uint precision = 16; 2355 enum Rounding roundingMode = Rounding.Up; 2356 } 2357 2358 auto d2 = decimal!(HigherHook)("10000000000000005"); 2359 assert(d2.rounded); 2360 assert(d2.toString() == "10000000000000010"); 2361 2362 auto d3 = decimal!(HigherHookDown)("10000000000000005"); 2363 assert(d3.rounded); 2364 assert(d3.toString() == "10000000000000000"); 2365 2366 auto d4 = decimal!(HigherHookUp)("10000000000000001"); 2367 assert(d4.rounded); 2368 assert(d4.toString() == "10000000000000010"); 2369 } 2370 2371 // mixing of different precisions 2372 @system 2373 unittest 2374 { 2375 static struct CustomHook 2376 { 2377 enum Rounding roundingMode = Rounding.HalfUp; 2378 enum uint precision = 19; 2379 } 2380 2381 auto d1 = decimal!(HighPrecision)("10000000000000000000"); 2382 auto d2 = decimal!(HighPrecision)("120000000000.0000"); 2383 auto d3 = decimal!(NoOp)("10000.00"); 2384 auto d4 = decimal!(CustomHook)("120000000000.0000"); 2385 auto d5 = d1 - d4; 2386 auto d6 = d4 - d1; 2387 auto d7 = d1 - d3; 2388 auto d8 = d1 * d4; 2389 auto d9 = d1 * d3; 2390 auto d10 = d4 * d1; 2391 auto d11 = d1 / d4; 2392 auto d12 = d4 / d1; 2393 auto d13 = d1 / d3; 2394 2395 assert(d1.opCmp(d2) == 1); 2396 assert(d1.opCmp(d3) == 1); 2397 assert(d3.opCmp(d4) == -1); 2398 assert(d5 == decimal!(HighPrecision)("9999999880000000000.0000")); 2399 assert(d6 == decimal!(CustomHook)("-9999999880000000000.0000")); 2400 assert(d7 == decimal!(HighPrecision)("9999999999999990000.00")); 2401 assert(d8 == decimal!(HighPrecision)("1200000000000000000000000000000.0000")); 2402 assert(d9 == decimal!(HighPrecision)("100000000000000000000000.00")); 2403 assert(d10 == decimal!(CustomHook)("1200000000000000000000000000000")); 2404 assert(d11 == decimal!(HighPrecision)("83333333.33333333333333333333333333333333333333333333333333333333")); 2405 assert(d12 == decimal!(CustomHook)("0.000000012")); 2406 assert(d13 == decimal!(HighPrecision)("1000000000000000")); 2407 } 2408 2409 /** 2410 * Factory function 2411 */ 2412 auto decimal(Hook = Abort, R)(R r) 2413 if ((isForwardRange!R && 2414 isSomeChar!(ElementEncodingType!R) && 2415 !isInfinite!R) || isNumeric!R) 2416 { 2417 return Decimal!(Hook)(r); 2418 } 2419 2420 /// 2421 @system 2422 unittest 2423 { 2424 auto d1 = decimal(5.5); 2425 assert(d1.toString == "5.5"); 2426 2427 auto d2 = decimal("500.555"); 2428 } 2429 2430 /** 2431 * Controls what happens when the number of significant digits exceeds `Hook.precision` 2432 */ 2433 enum Rounding 2434 { 2435 /** 2436 * Round toward 0, a.k.a truncate. The discarded digits are ignored. 2437 */ 2438 Down, 2439 /** 2440 * If the discarded digits represent greater than or equal to half (0.5) 2441 * of the value of a one in the next left position then the result coefficient 2442 * should be incremented by 1 (rounded up). Otherwise the discarded digits are ignored. 2443 */ 2444 HalfUp, 2445 /** 2446 * If the discarded digits represent greater than half (0.5) the value of a 2447 * one in the next left position then the result coefficient should be 2448 * incremented by 1 (rounded up). If they represent less than half, then the 2449 * result coefficient is not adjusted (that is, the discarded digits are ignored). 2450 * 2451 * Otherwise (they represent exactly half) the result coefficient is unaltered 2452 * if its rightmost digit is even, or incremented by 1 (rounded up) if its 2453 * rightmost digit is odd (to make an even digit). 2454 */ 2455 HalfEven, 2456 /** 2457 * If all of the discarded digits are zero or if the sign is 1 the result is 2458 * unchanged. Otherwise, the result coefficient should be incremented by 1 2459 * (rounded up). 2460 */ 2461 Ceiling, 2462 /** 2463 * If all of the discarded digits are zero or if the sign is 0 the result is 2464 * unchanged. Otherwise, the sign is 1 and the result coefficient should be 2465 * incremented by 1. 2466 */ 2467 Floor, 2468 /** 2469 * If the discarded digits represent greater than half (0.5) of the value of 2470 * a one in the next left position then the result coefficient should be 2471 * incremented by 1 (rounded up). Otherwise (the discarded digits are 0.5 or 2472 * less) the discarded digits are ignored. 2473 */ 2474 HalfDown, 2475 /** 2476 * (Round away from 0.) If all of the discarded digits are zero the result is 2477 * unchanged. Otherwise, the result coefficient should be incremented by 1 (rounded up). 2478 */ 2479 Up, 2480 /** 2481 * (Round zero or five away from 0.) The same as round-up, except that rounding 2482 * up only occurs if the digit to be rounded up is 0 or 5, and after overflow 2483 * the result is the same as for round-down. 2484 */ 2485 ZeroFiveUp 2486 } 2487 2488 /** 2489 * Will halt program on division by zero, invalid operations, 2490 * overflows, and underflows. 2491 * 2492 * Has 16 significant digits, rounds half up 2493 */ 2494 struct Abort 2495 { 2496 /// 2497 enum Rounding roundingMode = Rounding.HalfUp; 2498 /// 2499 enum uint precision = 16; 2500 /// 2501 enum int maxExponent = 999; 2502 /// 2503 enum int minExponent = -999; 2504 2505 /// 2506 static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T)) 2507 { 2508 assert(0, "Division by zero"); 2509 } 2510 2511 /// 2512 static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T)) 2513 { 2514 assert(0, "Invalid operation"); 2515 } 2516 2517 /// 2518 static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T)) 2519 { 2520 assert(0, "Overflow"); 2521 } 2522 2523 /// 2524 static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T)) 2525 { 2526 assert(0, "Underflow"); 2527 } 2528 } 2529 2530 /** 2531 * Same as abort, but offers 64 significant digits 2532 * 2533 * Note: As noted in the module overview, using 64 significant digits is 2534 * slower than `16` or `19`. 2535 */ 2536 struct HighPrecision 2537 { 2538 /// 2539 enum Rounding roundingMode = Rounding.HalfUp; 2540 /// 2541 enum uint precision = 64; 2542 /// 2543 enum int maxExponent = 999; 2544 /// 2545 enum int minExponent = -999; 2546 2547 /// 2548 static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T)) 2549 { 2550 assert(0, "Division by zero"); 2551 } 2552 2553 /// 2554 static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T)) 2555 { 2556 assert(0, "Invalid operation"); 2557 } 2558 2559 /// 2560 static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T)) 2561 { 2562 assert(0, "Overflow"); 2563 } 2564 2565 /// 2566 static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T)) 2567 { 2568 assert(0, "Underflow"); 2569 } 2570 } 2571 2572 /** 2573 * Will throw exceptions on division by zero, invalid operations, 2574 * overflows, and underflows 2575 * 2576 * Has 16 significant digits, rounds half up 2577 */ 2578 struct Throw 2579 { 2580 /// 2581 enum Rounding roundingMode = Rounding.HalfUp; 2582 /// 2583 enum uint precision = 16; 2584 /// 2585 enum int maxExponent = 999; 2586 /// 2587 enum int minExponent = -999; 2588 2589 /// 2590 static void onDivisionByZero(T)(T d) if (isInstanceOf!(Decimal, T)) 2591 { 2592 throw new DivisionByZero("Result: " ~ d.toString()); 2593 } 2594 2595 /// 2596 static void onInvalidOperation(T)(T d) if (isInstanceOf!(Decimal, T)) 2597 { 2598 throw new InvalidOperation("Result: " ~ d.toString()); 2599 } 2600 2601 /// 2602 static void onOverflow(T)(T d) if (isInstanceOf!(Decimal, T)) 2603 { 2604 throw new Overflow("Result: " ~ d.toString()); 2605 } 2606 2607 /// 2608 static void onUnderflow(T)(T d) if (isInstanceOf!(Decimal, T)) 2609 { 2610 throw new Underflow("Result: " ~ d.toString()); 2611 } 2612 } 2613 2614 /** 2615 * Does nothing on invalid operations except the proper flags 2616 * 2617 * Has 16 significant digits, rounds half up 2618 */ 2619 struct NoOp 2620 { 2621 /// 2622 enum Rounding roundingMode = Rounding.HalfUp; 2623 /// 2624 enum uint precision = 16; 2625 /// 2626 enum int maxExponent = 999; 2627 /// 2628 enum int minExponent = -999; 2629 } 2630 2631 /** 2632 * Thrown when using $(LREF Throw) and division by zero occurs 2633 */ 2634 class DivisionByZero : Exception 2635 { 2636 /++ 2637 Params: 2638 msg = The message for the exception. 2639 file = The file where the exception occurred. 2640 line = The line number where the exception occurred. 2641 next = The previous exception in the chain of exceptions, if any. 2642 +/ 2643 this(string msg, string file = __FILE__, size_t line = __LINE__, 2644 Throwable next = null) @nogc @safe pure nothrow 2645 { 2646 super(msg, file, line, next); 2647 } 2648 2649 /++ 2650 Params: 2651 msg = The message for the exception. 2652 next = The previous exception in the chain of exceptions. 2653 file = The file where the exception occurred. 2654 line = The line number where the exception occurred. 2655 +/ 2656 this(string msg, Throwable next, string file = __FILE__, 2657 size_t line = __LINE__) @nogc @safe pure nothrow 2658 { 2659 super(msg, file, line, next); 2660 } 2661 } 2662 2663 /** 2664 * Thrown when using $(LREF Throw) and an invalid operation occurs 2665 */ 2666 class InvalidOperation : Exception 2667 { 2668 /++ 2669 Params: 2670 msg = The message for the exception. 2671 file = The file where the exception occurred. 2672 line = The line number where the exception occurred. 2673 next = The previous exception in the chain of exceptions, if any. 2674 +/ 2675 this(string msg, string file = __FILE__, size_t line = __LINE__, 2676 Throwable next = null) @nogc @safe pure nothrow 2677 { 2678 super(msg, file, line, next); 2679 } 2680 2681 /++ 2682 Params: 2683 msg = The message for the exception. 2684 next = The previous exception in the chain of exceptions. 2685 file = The file where the exception occurred. 2686 line = The line number where the exception occurred. 2687 +/ 2688 this(string msg, Throwable next, string file = __FILE__, 2689 size_t line = __LINE__) @nogc @safe pure nothrow 2690 { 2691 super(msg, file, line, next); 2692 } 2693 } 2694 2695 /** 2696 * Thrown when using $(LREF Throw) and overflow occurs 2697 */ 2698 class Overflow : Exception 2699 { 2700 /++ 2701 Params: 2702 msg = The message for the exception. 2703 file = The file where the exception occurred. 2704 line = The line number where the exception occurred. 2705 next = The previous exception in the chain of exceptions, if any. 2706 +/ 2707 this(string msg, string file = __FILE__, size_t line = __LINE__, 2708 Throwable next = null) @nogc @safe pure nothrow 2709 { 2710 super(msg, file, line, next); 2711 } 2712 2713 /++ 2714 Params: 2715 msg = The message for the exception. 2716 next = The previous exception in the chain of exceptions. 2717 file = The file where the exception occurred. 2718 line = The line number where the exception occurred. 2719 +/ 2720 this(string msg, Throwable next, string file = __FILE__, 2721 size_t line = __LINE__) @nogc @safe pure nothrow 2722 { 2723 super(msg, file, line, next); 2724 } 2725 } 2726 2727 /** 2728 * Thrown when using $(LREF Throw) and underflow occurs 2729 */ 2730 class Underflow : Exception 2731 { 2732 /++ 2733 Params: 2734 msg = The message for the exception. 2735 file = The file where the exception occurred. 2736 line = The line number where the exception occurred. 2737 next = The previous exception in the chain of exceptions, if any. 2738 +/ 2739 this(string msg, string file = __FILE__, size_t line = __LINE__, 2740 Throwable next = null) @nogc @safe pure nothrow 2741 { 2742 super(msg, file, line, next); 2743 } 2744 2745 /++ 2746 Params: 2747 msg = The message for the exception. 2748 next = The previous exception in the chain of exceptions. 2749 file = The file where the exception occurred. 2750 line = The line number where the exception occurred. 2751 +/ 2752 this(string msg, Throwable next, string file = __FILE__, 2753 size_t line = __LINE__) @nogc @safe pure nothrow 2754 { 2755 super(msg, file, line, next); 2756 } 2757 } 2758 2759 /// Returns: If this decimal represents a positive or negative NaN 2760 bool isNaN(D)(D d) if (isInstanceOf!(Decimal, D)) 2761 { 2762 return d.isNan; 2763 } 2764 2765 /// 2766 @system unittest 2767 { 2768 assert( isNaN(decimal("NaN"))); 2769 assert( isNaN(decimal("-NaN"))); 2770 assert(!isNaN(decimal("Inf"))); 2771 assert(!isNaN(decimal("1.001"))); 2772 } 2773 2774 /// Returns: If this decimal represents positive or negative infinity 2775 bool isInfinity(D)(D d) if (isInstanceOf!(Decimal, D)) 2776 { 2777 return d.isInf; 2778 } 2779 2780 /// 2781 @system unittest 2782 { 2783 assert( isInfinity(decimal("Inf"))); 2784 assert( isInfinity(decimal("-Inf"))); 2785 assert(!isInfinity(decimal("NaN"))); 2786 assert(!isInfinity(decimal("1.001"))); 2787 } 2788 2789 /// Returns: The given decimal with a positive sign 2790 auto abs(D)(D d) if (isInstanceOf!(Decimal, D)) 2791 { 2792 if (d.sign == 1) 2793 return -d; 2794 return d; 2795 } 2796 2797 /// 2798 unittest 2799 { 2800 assert(abs(decimal("1.00")) == decimal("1.00")); 2801 assert(abs(decimal("-1.00")) == decimal("1.00")); 2802 assert(abs(decimal("-0.0002")) == decimal("0.0002")); 2803 } 2804 2805 private: 2806 2807 /* 2808 * Get the number of digits in the decimal representation of a number 2809 */ 2810 auto numberOfDigits(T)(T x) 2811 { 2812 if (x < 0) 2813 x *= -1; 2814 2815 immutable len = x.ulongLength; 2816 2817 if (len == 1) 2818 { 2819 if (x == 0UL) return 1; 2820 if (x < 10UL) return 1; 2821 if (x < 100UL) return 2; 2822 if (x < 1_000UL) return 3; 2823 if (x < 10_000UL) return 4; 2824 if (x < 100_000UL) return 5; 2825 if (x < 1_000_000UL) return 6; 2826 if (x < 10_000_000UL) return 7; 2827 if (x < 100_000_000UL) return 8; 2828 if (x < 1_000_000_000UL) return 9; 2829 if (x < 10_000_000_000UL) return 10; 2830 if (x < 100_000_000_000UL) return 11; 2831 if (x < 1_000_000_000_000UL) return 12; 2832 if (x < 10_000_000_000_000UL) return 13; 2833 if (x < 100_000_000_000_000UL) return 14; 2834 if (x < 1_000_000_000_000_000UL) return 15; 2835 if (x < 10_000_000_000_000_000UL) return 16; 2836 if (x < 100_000_000_000_000_000UL) return 17; 2837 if (x < 1_000_000_000_000_000_000UL) return 18; 2838 } 2839 2840 uint digits = 19; 2841 BigInt num = BigInt(10_000_000_000_000_000_000UL); 2842 2843 if (len == 3) 2844 { 2845 digits = 39; 2846 version(D_InlineAsm_X86) 2847 { 2848 num *= 10000000000000000000UL; 2849 num *= 10UL; 2850 } 2851 else 2852 { 2853 enum BigInt lentwo = BigInt("100000000000000000000"); 2854 num *= lentwo; 2855 } 2856 } 2857 else if (len == 4) 2858 { 2859 digits = 58; 2860 version(D_InlineAsm_X86) 2861 { 2862 num *= 10000000000000000000UL; 2863 num *= 10000000000000000000UL; 2864 num *= 10UL; 2865 } 2866 else 2867 { 2868 enum BigInt lenthree = BigInt("1000000000000000000000000000000000000000"); 2869 num *= lenthree; 2870 } 2871 } 2872 else if (len > 4) 2873 { 2874 digits = 78; 2875 version(D_InlineAsm_X86) 2876 { 2877 num *= 10000000000000000000UL; 2878 num *= 10000000000000000000UL; 2879 num *= 10000000000000000000UL; 2880 num *= 100UL; 2881 } 2882 else 2883 { 2884 enum BigInt lenfour = BigInt("100000000000000000000000000000000000000000000000000000000000"); 2885 num *= lenfour; 2886 } 2887 } 2888 2889 for (;; num *= 10, digits++) 2890 { 2891 if (x < num) 2892 return digits; 2893 } 2894 } 2895 2896 @system pure 2897 unittest 2898 { 2899 import std.bigint; 2900 import std.range : chain, repeat; 2901 import std.utf : byCodeUnit; 2902 2903 assert(numberOfDigits(BigInt("0")) == 1); 2904 assert(numberOfDigits(BigInt("1")) == 1); 2905 assert(numberOfDigits(BigInt("1_000")) == 4); 2906 assert(numberOfDigits(BigInt("-1_000")) == 4); 2907 assert(numberOfDigits(BigInt("1_000_000")) == 7); 2908 assert(numberOfDigits(BigInt("123_456")) == 6); 2909 assert(numberOfDigits(BigInt("123_456")) == 6); 2910 assert(numberOfDigits(BigInt("123_456_789_101_112_131_415_161")) == 24); 2911 assert(numberOfDigits(BigInt("1_000_000_000_000_000_000_000_000")) == 25); 2912 assert(numberOfDigits(BigInt("1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000")) == 46); 2913 assert(numberOfDigits(BigInt("1".byCodeUnit.chain('0'.repeat(60)))) == 61); 2914 assert(numberOfDigits(BigInt("1".byCodeUnit.chain('0'.repeat(99)))) == 100); 2915 } 2916 2917 /* 2918 * Detect whether $(D X) is an enum type, or manifest constant. 2919 */ 2920 template isEnum(X...) if (X.length == 1) 2921 { 2922 static if (is(X[0] == enum)) 2923 { 2924 enum isEnum = true; 2925 } 2926 else static if (!is(X[0]) && 2927 !is(typeof(X[0]) == void) && 2928 !isFunction!(X[0])) 2929 { 2930 enum isEnum = 2931 !is(typeof({ auto ptr = &X[0]; })) 2932 && !is(typeof({ enum off = X[0].offsetof; })); 2933 } 2934 else 2935 enum isEnum = false; 2936 }