1 /* This file is part of the Amalthea library. 2 * 3 * Copyright (C) 2020-2021 Eugene 'Vindex' Stulin 4 * 5 * Distributed under the Boost Software License 1.0 or (at your option) 6 * the GNU Lesser General Public License 3.0 or later. 7 */ 8 9 module amalthea.decimal; 10 11 public import amalthea.libcore; 12 import amalthea.dataprocessing : makeFilledArray; 13 14 import std.array, std.algorithm, std.bigint, std.format, std.math, std.string; 15 16 private size_t defaultFixedPoint = 18; 17 18 19 /******************************************************************************* 20 * Sets default value of fixed point positon for all new Decimal structures. 21 */ 22 void setDefaultFixedPoint(size_t fixedPoint) { 23 defaultFixedPoint = fixedPoint; 24 } 25 26 27 /******************************************************************************* 28 * Gets current value of fixed point position by default. 29 * If the value was not changed by setDefaultFixedPoint, the value is 18. 30 */ 31 size_t getDefaultFixedPoint() { 32 return defaultFixedPoint; 33 } 34 35 36 /******************************************************************************* 37 * Transforms number or string to Decimal. 38 */ 39 Decimal toDecimal(T)(T value) { 40 return Decimal(value); 41 } 42 43 44 /// Short alias for main structure. 45 alias D = Decimal; 46 47 48 /******************************************************************************* 49 * A struct representing a decimal fixed-point number. 50 * 51 * By default, the number of digits after the decimal point is 18. 52 * The decimal point position is limited 53 * by the maximum value of the size_t data type. 54 * Decimal is based on std.bigint.BigInt and have no limitation 55 * for the number of significant digits. 56 */ 57 struct Decimal { 58 private BigInt value; 59 private size_t fixedPoint; 60 61 /*************************************************************************** 62 * Constucts an object from a decimal string. 63 * 64 * Params: 65 * number = Decimal string with comma/point or without it. 66 * fixedPointPosition = Position of decimal point in new object. 67 */ 68 this(string number, size_t fixedPointPosition = defaultFixedPoint) { 69 fixedPoint = fixedPointPosition; 70 number = number.replace(",", "."); 71 auto twoParts = number.split("."); 72 if (twoParts.length > 2 || !std..string.isNumeric(number)) { 73 throw new DecimalException("Wrong data format"); 74 } 75 76 auto integer = twoParts[0].to!(char[]); 77 int sign = 1; 78 if (integer[0] == '-') { 79 sign = -1; 80 } 81 if (fixedPoint != 0) { 82 integer.length += fixedPoint; 83 integer[$-fixedPoint .. $] = '0'; 84 } 85 value = BigInt(integer); 86 87 if (twoParts.length == 2 && fixedPoint != 0) { 88 auto fraction = twoParts[1].to!(char[]); 89 if (fraction.length > fixedPoint) { 90 fraction.length = fixedPoint; 91 } else { 92 size_t oldLength = fraction.length; 93 fraction.length = fixedPoint; 94 fraction[oldLength .. $] = '0'; 95 } 96 value += sign * BigInt(fraction); 97 } 98 } 99 100 /*************************************************************************** 101 * Constucts an object from another Decimal object. 102 * This constructor is used to transforming precision. 103 * 104 * Params: 105 * number = Decimal object. 106 * fixedPointPosition = Position of decimal point in new object. 107 */ 108 this(Decimal number, size_t fixedPointPosition = defaultFixedPoint) { 109 fixedPoint = fixedPointPosition; 110 if (fixedPoint == number.fixedPoint) { 111 value = number.value; 112 } else if (fixedPoint > number.fixedPoint) { 113 auto diff = fixedPoint - number.fixedPoint; 114 value = number.value * (BigInt(10)^^diff); 115 } else if (number.fixedPoint > fixedPoint) { 116 auto diff = number.fixedPoint - fixedPoint; 117 BigInt rem; 118 BigInt divisor = BigInt(10)^^diff; 119 divMod(number.value, divisor, value, rem); 120 // if the remainder is one order of magnitude less than the divisor 121 if (divisor < absBigInt(rem*10)) { 122 value += calcRoundingCompensation(rem); 123 } 124 } 125 } 126 127 /*************************************************************************** 128 * Constucts an object from a BigInt object. 129 * 130 * Params: 131 * number = BigInt 132 * fixedPointPosition = Position of decimal point in new object. 133 */ 134 this(BigInt number, size_t fixedPointPosition = defaultFixedPoint) { 135 fixedPoint = fixedPointPosition; 136 value = number * BigInt(10)^^fixedPoint; 137 } 138 139 /*************************************************************************** 140 * Constucts an object from an integer number (int/uint, long/ulong, etc.). 141 * 142 * Params: 143 * number = Integer number of a built-in data type. 144 * fixedPointPosition = Position of decimal point in new object. 145 */ 146 this(T)(T number, size_t fixedPointPosition = defaultFixedPoint) 147 if (is(T: long)) { 148 fixedPoint = fixedPointPosition; 149 value = BigInt(number) * BigInt(10)^^fixedPoint; 150 } 151 152 /*************************************************************************** 153 * Constucts an object from a floating point number. 154 * (For good precision, 155 * give preference to forming a decimal number from a string.) 156 * 157 * Params: 158 * number = Floating point number of a built-in data type. 159 * fixedPointPosition = Position of decimal point in new object. 160 */ 161 this(real number, size_t fixedPointPosition = defaultFixedPoint) { 162 if (std.math.isNaN(number) || std.math.isInfinity(number)) { 163 throw new DecimalException("Unsupported floating value"); 164 } 165 string fmt = "%f"; 166 string strValue = format(fmt, number); 167 this(strValue, fixedPointPosition); 168 } 169 170 /*************************************************************************** 171 * Converts to a human readable decimal string. 172 * All characters after the dot (with all zeros) will be shown according 173 * to the configured fixed point position. 174 */ 175 string toFullString() const { 176 auto strValue = format!"%d"(value).to!(char[]); 177 int sign = 1; 178 if (strValue[0] == '-') { 179 sign = -1; 180 strValue = strValue[1 .. $]; 181 } 182 if (strValue.length <= fixedPoint) { 183 char[] zeros; 184 zeros.length = fixedPoint - strValue.length; 185 zeros[] = '0'; 186 strValue = "0" ~ zeros ~ strValue; 187 } 188 auto res = strValue[0 .. $-fixedPoint]; 189 if (fixedPoint != 0) { 190 res ~= "." ~ strValue[$-fixedPoint .. $]; 191 } 192 if (sign == -1) { 193 res = "-" ~ res; 194 } 195 return res.to!string; 196 } 197 198 /*************************************************************************** 199 * Convert the `Decimal` to `string`, passing it to the given sink. 200 * 201 * Params: 202 * sink = An OutputRange for accepting possibly piecewise 203 * segments of the formatted string. 204 * fmt = A format string specifying the output format. 205 * 206 * Available: %D (or %f instead it) and %s. 207 * For %D (or %f) there are available '+', '0' and width with precision 208 * like for floating. For example, "%+08.5D". 209 */ 210 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) 211 const { 212 immutable defaultFormatPrecision = fmt.UNSPECIFIED; 213 if (fmt.spec == 'D' || fmt.spec == 'f') { 214 auto prec = fmt.precision; 215 if (prec == defaultFormatPrecision && this.fixedPoint < int.max) { 216 prec = cast(int)this.fixedPoint; 217 } 218 string[] parts = this.toFullString().split('.'); 219 if (parts.length == 1) { 220 parts ~= makeFilledArray(prec, '0').idup; 221 } else { 222 if (prec > this.fixedPoint) { 223 parts[1] ~= makeFilledArray(prec-this.fixedPoint, '0'); 224 } else { 225 auto rounded = this.round(prec); 226 string[] roundedParts = rounded.toFullString().split('.'); 227 parts[0] = roundedParts[0]; 228 if (parts.length == 2) { 229 parts[1] = roundedParts[1][0 .. prec]; 230 } else { 231 parts[1] = ""; 232 } 233 } 234 } 235 string result = parts[1] != "" ? parts[0] ~ "." ~ parts[1] 236 : parts[0]; 237 if (result.length < fmt.width) { 238 char filler = fmt.flZero ? '0' : ' '; 239 char[] s = makeFilledArray(fmt.width-result.length, filler); 240 if (fmt.flPlus) s[0] = '+'; 241 result = s.idup ~ result; 242 } else if (fmt.flPlus) { 243 result = "+" ~ result; 244 } 245 sink(result); 246 } else if (fmt.spec == 's') { 247 sink(this.toFullString.stripRight('0').stripRight('.')); 248 } else { 249 throw new Exception("Unknown format specifier: %" ~ fmt.spec); 250 } 251 } 252 253 /*************************************************************************** 254 * Converts to a boolean value. 255 */ 256 T opCast(T:bool)() { 257 return !isZero(); 258 } 259 260 /*************************************************************************** 261 * Converts to a floating type, if possible. 262 */ 263 T opCast(T:real)() { 264 return this.toFullString.stripRight('0').to!T; 265 } 266 267 /*************************************************************************** 268 * Converts to an integer type, if possible. 269 */ 270 T opCast(T:long)() { 271 string integerPart = this.toFullString().split(".")[0]; 272 return integerPart.to!T; 273 } 274 275 /*************************************************************************** 276 * Gets the modulus. 277 */ 278 Decimal abs() pure const { 279 auto copy = this; 280 if (this.value < 0) { 281 return -copy; 282 } 283 return copy; 284 } 285 286 /*************************************************************************** 287 * Calculates and returns the rounded value. 288 * The fixed point position in the return value remains the original. 289 * Params: 290 * lastDigitPosition = The number of decimal places to save. 291 * By default, 0. 292 */ 293 Decimal round(size_t lastDigitPosition = 0) pure const { 294 Decimal copy = this; 295 if (fixedPoint == 0 || lastDigitPosition >= fixedPoint) { 296 return copy; 297 } 298 auto diff = fixedPoint - lastDigitPosition; 299 BigInt rem; 300 BigInt divisor = BigInt(10)^^diff; 301 divMod(copy.value, divisor, copy.value, rem); 302 if (divisor < absBigInt(rem*10)) { 303 copy.value += calcRoundingCompensation(rem); 304 } 305 copy.value *= BigInt(10)^^diff; 306 return copy; 307 } 308 309 /*************************************************************************** 310 * Gets a sign of the decimal number (-1, 0 or 1). 311 */ 312 int getSign() pure const { 313 if (this.value < 0) { 314 return -1; 315 } else if (this.value == 0) { 316 return 0; 317 } 318 return 1; 319 } 320 321 /*************************************************************************** 322 * Implements Decimal equality test with other Decimal. 323 */ 324 bool opEquals(Decimal rhs) { 325 string left = this.toFullString.stripRight('0').stripRight('.'); 326 string right = rhs.toFullString.stripRight('0').stripRight('.'); 327 return left == right; 328 } 329 330 /*************************************************************************** 331 * Implements Decimal equality test with buil-in numeric types and strings. 332 */ 333 bool opEquals(T)(T rhs) { 334 auto right = Decimal(rhs); 335 return this.opEquals(right); 336 } 337 338 /*************************************************************************** 339 * Implements comparison of Decimal with other Decimal. 340 */ 341 int opCmp(Decimal rhs) { 342 if (this.opEquals(rhs)) { 343 return 0; 344 } 345 if (this.fixedPoint == rhs.fixedPoint && this.value > rhs.value) { 346 return 1; 347 } else if (this.fixedPoint != rhs.fixedPoint) { 348 auto lhs = this; 349 leadToUnifiedFixedPoint(lhs, rhs); 350 if (lhs.value > rhs.value) { 351 return 1; 352 } 353 } 354 return -1; 355 } 356 357 /*************************************************************************** 358 * Implements comparison of Decimal with other numeric types and strings. 359 */ 360 int opCmp(T)(T rhs) { 361 return this.opCmp(Decimal(rhs)); 362 } 363 364 /*************************************************************************** 365 * Implements Decimal unary operator "++". 366 */ 367 ref Decimal opUnary(string op)() if (op == "++") { 368 auto appendum = BigInt(1) * BigInt(10)^^fixedPoint; 369 value += appendum; 370 return this; 371 } 372 373 /*************************************************************************** 374 * Implements Decimal unary operator "--". 375 */ 376 ref Decimal opUnary(string op)() if (op == "--") { 377 auto subtrahend = BigInt(1) * BigInt(10)^^fixedPoint; 378 value -= subtrahend; 379 return this; 380 } 381 382 /*************************************************************************** 383 * Implements Decimal unary operator "+". 384 */ 385 Decimal opUnary(string op)() if (op == "+") { 386 return this; 387 } 388 389 /*************************************************************************** 390 * Implements Decimal unary operator "-". 391 */ 392 Decimal opUnary(string op)() pure const if (op == "-") { 393 Decimal res = this; 394 res.value = -this.value; 395 return res; 396 } 397 398 /*************************************************************************** 399 * Implements binary operators between Decimals. 400 * Returns an object with the maximum precision of both original objects. 401 */ 402 Decimal opBinary(string op)(Decimal rhs) const 403 if (op.among("+", "-", "*", "/", "^^")) { 404 static if (op == "^^") { 405 return pow(this, rhs); 406 } 407 408 Decimal lhs = this; //copied current state 409 size_t bestPrecision = leadToUnifiedFixedPoint(lhs, rhs); 410 auto res = Decimal(0, bestPrecision); 411 412 static if (op == "+") { 413 res.value = lhs.value + rhs.value; 414 } 415 static if (op == "-") { 416 res.value = lhs.value - rhs.value; 417 } 418 static if (op == "*") { 419 res.value = lhs.value * rhs.value; 420 BigInt rem; 421 BigInt divisor = BigInt(10)^^bestPrecision; 422 divMod(res.value, divisor, res.value, rem); 423 if (divisor < absBigInt(rem*10)) { 424 res.value += calcRoundingCompensation(rem); 425 } 426 } 427 static if (op == "/") { 428 if (rhs.value == BigInt(0)) { 429 throw new DecimalException("Division by zero"); 430 } 431 BigInt rem; 432 lhs.value *= BigInt(10)^^bestPrecision; 433 divMod(lhs.value, rhs.value, res.value, rem); 434 if (rhs.value < absBigInt(rem*10)) { 435 res.value += calcRoundingCompensation(rem); 436 } 437 } 438 return res; 439 } 440 441 /*************************************************************************** 442 * Implements binary operators between Decimal and build-in type values. 443 * 444 * The precision of the decimal result is equal 445 * to the precision of the decimal argument. 446 */ 447 Decimal opBinary(string op, T)(T rhs) const 448 if (op.among("+", "-", "*", "/", "^^")) { 449 static if (op == "^^") { 450 return pow(this, Decimal(rhs, this.fixedPoint)); 451 } 452 return this.opBinary!op(Decimal(rhs, this.fixedPoint)); 453 } 454 455 /*************************************************************************** 456 * Implements operators with built-in integers on the left-hand side 457 * and `Decimal` on the right-hand side. 458 * 459 * The precision of the decimal result is equal 460 * to the precision of the decimal argument. 461 */ 462 Decimal opBinaryRight(string op, T)(T value) const 463 if (op.among("+", "*", "-", "/", "^^")) { 464 auto decValue = Decimal(value, this.fixedPoint); 465 static if (op == "^^") { 466 return pow(decValue, this); 467 } 468 return decValue.opBinary!op(this); 469 } 470 471 /*************************************************************************** 472 * Implements assignment operators of the form `Decimal op= integer`: 473 * "+=", "-=", "*=", "/=", "^^=". 474 * Position of the fixed point (precision) doesn't change. 475 */ 476 Decimal opOpAssign(string op, T)(T value) 477 if (op.among("+", "-", "*", "/", "^^")) { 478 Decimal rhs = Decimal(value, this.fixedPoint); 479 static if (op == "+") this.value += rhs.value; 480 static if (op == "-") this.value -= rhs.value; 481 static if (op == "*") { 482 this.value *= rhs.value; 483 BigInt rem; 484 BigInt divisor = BigInt(10)^^this.fixedPoint; 485 divMod(this.value, divisor, this.value, rem); 486 if (divisor < absBigInt(rem*10)) { 487 this.value += calcRoundingCompensation(rem); 488 } 489 } 490 static if (op == "/") { 491 if (rhs.value == BigInt(0)) { 492 throw new DecimalException("Division by zero"); 493 } 494 this.value *= BigInt(10)^^this.fixedPoint; 495 BigInt rem; 496 divMod(this.value, rhs.value, this.value, rem); 497 if (rhs.value < absBigInt(rem*10)) { 498 this.value += calcRoundingCompensation(rem); 499 } 500 } 501 static if (op == "^^") { 502 Decimal res = pow(this, rhs); 503 this = Decimal(res, this.fixedPoint); 504 } 505 return this; 506 } 507 508 /*************************************************************************** 509 * Check if Decimal has zero fractional part. 510 */ 511 bool isInteger() { 512 if (fixedPoint == 0) { 513 return true; 514 } 515 string fractionalPart = toFullString().split(".")[1]; 516 return all!"a=='0'"(fractionalPart); 517 } 518 519 /*************************************************************************** 520 * Check if Decimal has zero value. 521 */ 522 bool isZero() { 523 return this.value == 0; 524 } 525 526 private: 527 528 size_t leadToUnifiedFixedPoint(ref Decimal lhs, ref Decimal rhs) 529 pure const { 530 size_t bestPrecision, worstPrecision; 531 if (lhs.fixedPoint > rhs.fixedPoint) { 532 bestPrecision = lhs.fixedPoint; 533 worstPrecision = rhs.fixedPoint; 534 rhs.value *= BigInt(10)^^(bestPrecision-worstPrecision); 535 } else { 536 worstPrecision = this.fixedPoint; 537 bestPrecision = rhs.fixedPoint; 538 lhs.value *= BigInt(10)^^(bestPrecision-worstPrecision); 539 } 540 return bestPrecision; 541 } 542 543 BigInt calcSignForBigInt(BigInt value) pure const { 544 if (value > 0) return BigInt(1); 545 if (value == 0) return BigInt(0); 546 return BigInt(-1); 547 } 548 549 BigInt absBigInt(BigInt value) pure const { 550 return value >= 0 ? value : -value; 551 } 552 553 BigInt calcRoundingCompensation(BigInt remainder) pure const { 554 BigInt absRemainder = absBigInt(remainder); 555 BigInt sign = calcSignForBigInt(remainder); 556 if (remainder % 10 == remainder) { 557 if (remainder >= 0) { 558 if (remainder % 10 >= 5) { 559 return BigInt(1); 560 } 561 } else { 562 if (-remainder % 10 >= 5) { 563 return BigInt(-1); 564 } 565 } 566 return BigInt(0); 567 } 568 size_t calcDigits(BigInt value) { 569 return absBigInt(value).toDecimalString.length; 570 } 571 if (absRemainder % 10 >= 5) { 572 if (calcDigits(absRemainder) != calcDigits(absRemainder + 1)) { 573 return sign; // overflow 574 } 575 remainder = (remainder + sign) / 10; 576 } else { 577 remainder = remainder / 10; 578 } 579 return calcRoundingCompensation(remainder); 580 } 581 } 582 583 584 /******************************************************************************* 585 * Calculates e^x for Decimal. 586 */ 587 Decimal exp(Decimal x) { 588 auto precision = x.fixedPoint; 589 auto additPrecision = precision * 2 + 3; 590 Decimal res = x + Decimal(1, additPrecision); 591 Decimal numerator = Decimal(x, additPrecision); 592 Decimal denominator = Decimal(1, additPrecision); 593 foreach (i; 2 .. additPrecision) { 594 numerator = numerator * x; 595 denominator = denominator * i; 596 res = res + numerator/denominator; 597 } 598 return Decimal(res, precision); 599 } 600 601 602 /******************************************************************************* 603 * Calculates the natural logarithm x (for `real`). 604 * The precision is 18 decimal places. 605 */ 606 real ln(real x) { 607 auto numerator = (x-1)/(x+1); 608 auto n2 = numerator * numerator; 609 auto res = numerator; 610 real elem; // row element 611 real denominator = 3; 612 do { 613 numerator = numerator * n2; 614 elem = numerator / denominator; 615 res = res + elem; 616 denominator = denominator + 2; 617 } while (elem > 0.000000000000000001); 618 619 return res * 2; 620 } 621 622 623 /******************************************************************************* 624 * Calculates the natural logarithm x of the Decimal type. 625 */ 626 Decimal ln(Decimal x) { 627 auto precision = x.fixedPoint; 628 auto additPrecision = x.fixedPoint * 2 + 3; 629 auto extendedX = Decimal(x, additPrecision); 630 auto numerator = (extendedX-1)/(extendedX+1); 631 auto n2 = numerator * numerator; 632 auto res = numerator; 633 Decimal elem = Decimal(0, additPrecision); // row element 634 Decimal denominator = Decimal(3, additPrecision); 635 char[] zeros = makeFilledArray(precision, '0'); 636 string strMin = "0." ~ zeros.idup ~ "1"; 637 auto minValue = Decimal(strMin, additPrecision); 638 do { 639 numerator = numerator * n2; 640 elem = numerator / denominator; 641 res = res + elem; 642 denominator = denominator + 2; 643 } while (elem > minValue); 644 645 res.value *= 2; 646 return Decimal(res, x.fixedPoint); 647 } 648 649 650 /******************************************************************************* 651 * Calculates the logarithm. 652 * 653 * Params: 654 * b = Decimal value 655 * a = base of the logarithm 656 */ 657 Decimal log(Decimal b, Decimal a) { 658 auto precision = b.fixedPoint > a.fixedPoint ? b.fixedPoint : a.fixedPoint; 659 auto additPrecison = precision * 2 + 3; 660 b = Decimal(b, additPrecison); 661 a = Decimal(a, additPrecison); 662 return Decimal(ln(b) / ln(a), precision); 663 } 664 665 666 /******************************************************************************* 667 * Returns the value of x raised to the power of y. 668 * 669 * Params: 670 * x = Decimal raised to a power 671 * y = exponent of the ulong type 672 */ 673 Decimal pow(Decimal x, ulong y) { 674 if (y == 0) { 675 return Decimal(1, x.fixedPoint); 676 } else if (y == 1) { 677 return x; 678 } 679 Decimal res = x; 680 foreach(i; 1 .. y) { 681 res = res * x; 682 } 683 return res; 684 } 685 686 687 /******************************************************************************* 688 * Returns the value of x raised to the power of y. 689 * 690 * Params: 691 * x = Decimal raised to a power 692 * y = Decimal exponent 693 */ 694 Decimal pow(Decimal x, Decimal y) { 695 size_t digits = x.fixedPoint > y.fixedPoint ? x.fixedPoint : y.fixedPoint; 696 x = Decimal(x, digits*2+8); 697 y = Decimal(y, digits*2+8); 698 auto res = exp(y * ln(x)); 699 return Decimal(res, digits); 700 } 701 702 703 /******************************************************************************* 704 * Base exeption for this module 705 */ 706 class DecimalException : Exception { mixin RealizeException; } 707 708 709 710 /******************************************************************************* 711 ********************************** UNITTESTS *********************************** 712 *******************************************************************************/ 713 714 // constructors 715 unittest { 716 auto x = Decimal("36"); 717 assert(x.value == BigInt("36000000000000000000")); 718 assert(x.toFullString == "36.000000000000000000"); 719 720 x = Decimal("36.6"); 721 assert(x.value == BigInt("36600000000000000000")); 722 assert(x.toFullString == "36.600000000000000000"); 723 724 x = Decimal("36,6"); 725 assert(x.value == BigInt("36600000000000000000")); 726 assert(x.toFullString == "36.600000000000000000"); 727 728 x = Decimal("-36,6"); 729 assert(x.value == BigInt("-36600000000000000000")); 730 assert(x.toFullString == "-36.600000000000000000"); 731 732 x = Decimal(36); 733 assert(x.value == BigInt("36000000000000000000")); 734 assert(x.toFullString == "36.000000000000000000"); 735 736 x = Decimal(-36); 737 assert(x.value == BigInt("-36000000000000000000")); 738 assert(x.toFullString == "-36.000000000000000000"); 739 740 x = Decimal(-36.6, 4); 741 assert(x.value == BigInt("-366000")); 742 assert(x.toFullString == "-36.6000"); 743 744 x = Decimal("0"); 745 assert(x.value == BigInt("0")); 746 assert(x.toFullString == "0.000000000000000000"); 747 748 x = Decimal(0); 749 assert(x.value == BigInt("0")); 750 assert(x.toFullString == "0.000000000000000000"); 751 752 x = Decimal(0.0); 753 assert(x.value == BigInt("0")); 754 assert(x.toFullString == "0.000000000000000000"); 755 756 x = Decimal(-0.0); 757 assert(x.value == BigInt("0")); 758 assert(x.toFullString == "0.000000000000000000"); 759 760 x = Decimal("0.12"); 761 assert(x.value == BigInt("120000000000000000")); 762 assert(x.toFullString == "0.120000000000000000"); 763 764 x = Decimal(0.12); 765 assert(x.value == BigInt("120000000000000000")); 766 assert(x.toFullString == "0.120000000000000000"); 767 768 x = Decimal("-0.12"); 769 assert(x.value == BigInt("-120000000000000000")); 770 assert(x.toFullString == "-0.120000000000000000"); 771 772 x = Decimal(-0.12); 773 assert(x.value == BigInt("-120000000000000000")); 774 assert(x.toFullString == "-0.120000000000000000"); 775 776 x = Decimal("-0.02"); 777 assert(x.value == BigInt("-20000000000000000")); 778 assert(x.toFullString == "-0.020000000000000000"); 779 780 x = Decimal(-0.02); 781 assert(x.value == BigInt("-20000000000000000")); 782 assert(x.toFullString == "-0.020000000000000000"); 783 784 x = Decimal("0.1"); 785 assert(x.value == BigInt("100000000000000000")); 786 assert(x.toFullString == "0.100000000000000000"); 787 788 x = Decimal(0.1); 789 assert(x.value == BigInt("100000000000000000")); 790 assert(x.toFullString == "0.100000000000000000"); 791 792 x = Decimal("-0.00123"); 793 assert(x.value == BigInt("-1230000000000000")); 794 assert(x.toFullString == "-0.001230000000000000"); 795 796 x = Decimal(-0.00123); 797 assert(x.value == BigInt("-1230000000000000")); 798 assert(x.toFullString == "-0.001230000000000000"); 799 800 x = Decimal(BigInt(36)); 801 assert(x.value == BigInt("36000000000000000000")); 802 assert(x.toFullString == "36.000000000000000000"); 803 } 804 805 // error data 806 unittest { 807 bool error = false; 808 Decimal x; 809 try x = Decimal("34.2.55"); 810 catch (DecimalException e) error = true; 811 assert(error); 812 813 error = false; 814 try x = Decimal(""); 815 catch (DecimalException e) error = true; 816 assert(error); 817 818 error = false; 819 try x = Decimal("23.1A"); 820 catch (DecimalException e) error = true; 821 assert(error); 822 823 error = false; 824 try x = Decimal(float.infinity); 825 catch(DecimalException e) error = true; 826 assert(error); 827 828 error = false; 829 try x = Decimal(float.nan); 830 catch(DecimalException e) error = true; 831 assert(error); 832 833 error = false; 834 try x = Decimal(2)/Decimal(0); 835 catch(DecimalException e) error = true; 836 assert(error); 837 838 error = false; 839 x = Decimal("9999999999999999999999999999999999"); 840 try writefln("%d", x.to!long); 841 catch(std.conv.ConvOverflowException e) error = true; 842 assert(error); 843 844 error = false; 845 x = Decimal(65536); 846 try writefln("%d", x.to!ushort); 847 catch(std.conv.ConvOverflowException e) error = true; 848 assert(error); 849 } 850 851 //transform/copy constructor 852 unittest { 853 auto x = Decimal("6.54321", 6); 854 assert(x.toFullString == "6.543210"); 855 auto y = Decimal(x, 4); 856 assert(x != y); 857 assert(y.toFullString == "6.5432"); 858 auto z = Decimal(x, 7); 859 assert(x == z); 860 assert(z.toFullString == "6.5432100"); 861 } 862 863 864 //convertation 865 unittest { 866 auto x = Decimal("5", 4); 867 assert(x.toFullString == "5.0000"); 868 assert(x.to!string == "5"); 869 assert(x.to!real == 5.0); 870 assert(x.to!long == 5); 871 x = Decimal("5.8"); 872 assert(x.to!long == 5); 873 x = Decimal("-5.8"); 874 assert(x.to!long == -5); 875 assert(x.to!bool == true); 876 x = Decimal("0.0000000000000000000000000000001", 50); 877 assert(x.to!bool == true); 878 x = Decimal(0); 879 assert(x.to!bool == false); 880 } 881 882 // usage of output format 883 unittest { 884 Decimal x = Decimal("5.123456789"); 885 assert(format!"%2.7D"(x) == "5.1234568"); 886 assert(format!"%.7D"(x) == "5.1234568"); 887 assert(format!"%.12D"(x) == "5.123456789000"); 888 assert(format!"%D"(x) == "5.123456789000000000"); 889 assert(format!"%8.2D"(x) == " 5.12"); 890 assert(format!"%08.2D"(x) == "00005.12"); 891 assert(format!"%+08.2D"(x) == "+0005.12"); 892 assert(format!"%+.2D"(x) == "+5.12"); 893 assert(format!"%+.0D"(x) == "+5"); 894 Decimal zero = Decimal(0); 895 assert(format!"%.8D"(zero) == "0.00000000"); 896 assert(format!"%D"(zero) == "0.000000000000000000"); 897 assert(format!"%s"(zero) == "0"); 898 } 899 900 901 // comparison 902 unittest { 903 auto x = Decimal(-36.6, 4); 904 assert(Decimal("-36.6") == x); 905 906 assert(Decimal("0") == Decimal(-0.0)); 907 908 assert(Decimal(-0.0002) < Decimal(-0.0001)); 909 assert(Decimal(-0.0001) >= Decimal(-0.0001)); 910 assert(Decimal(-0.0001, 8) == Decimal(-0.0001, 5)); 911 assert(Decimal(-0.0003, 17) == Decimal(-0.0003, 18)); 912 assert(Decimal(0.0004, 5) > Decimal(0.00035, 8)); 913 assert(Decimal(0.0004, 8) > Decimal(0.00035, 5)); 914 assert(Decimal(-0.0004, 8) < Decimal(0.00035, 5)); 915 assert(Decimal(-0.0004, 5) < Decimal(0.00035, 8)); 916 917 assert(Decimal(1) == 1); 918 assert(1 == Decimal(1)); 919 920 assert(Decimal(100.5) == 100.5); 921 assert(Decimal(100.6) > 100.5); 922 } 923 924 925 // precision 926 unittest { 927 auto x = Decimal("36.6", 4); 928 assert(x.toFullString == "36.6000"); 929 930 x = Decimal("36,654321", 3); 931 assert(x.toFullString == "36.654"); 932 933 x = Decimal("-36,654321", 3); 934 assert(x.toFullString == "-36.654"); 935 936 x = Decimal(-36.654321, 3); 937 assert(x.toFullString == "-36.654"); 938 939 x = Decimal("36,654321", 0); 940 assert(x.toFullString == "36"); 941 942 x = Decimal("-36,654321", 0); 943 assert(x.toFullString == "-36"); 944 945 x = Decimal(-36.654321, 0); 946 assert(x.toFullString == "-36"); 947 948 x = Decimal("-36.654321", 0); 949 assert(x.toFullString == "-36"); 950 951 x = Decimal("-0.03", 0); 952 assert(x.toFullString == "0"); 953 954 x = Decimal(100, 2); 955 assert(x.toFullString == "100.00"); 956 957 x = Decimal(100, 0); 958 assert(x.toFullString == "100"); 959 960 // round 961 x = Decimal(99.79, 2); 962 assert(Decimal(x, 1).toFullString == "99.8"); 963 } 964 965 //unary operators 966 unittest { 967 auto x = Decimal(0); 968 x++; 969 assert(x == Decimal(1)); 970 971 x = Decimal(-0.4); 972 x++; 973 assert(x == Decimal(0.6)); 974 x--; 975 assert(x == Decimal(-0.4)); 976 977 x = Decimal(0); 978 x--; 979 assert(x == Decimal(-1)); 980 981 x = Decimal(-9); 982 assert(x == -Decimal(9)); 983 984 x = -Decimal(0.3); 985 assert(x == Decimal(-0.3)); 986 } 987 988 // plus/minus 989 unittest { 990 assert(2.3.D + 7.8.D == 10.1.D); 991 assert(Decimal("0.5") + Decimal("-0.7") == Decimal("-0.2")); 992 assert(Decimal("20.9") - Decimal("5.87") == Decimal("15.03")); 993 assert(Decimal("20.39", 2) + Decimal(11.125, 3) == Decimal("31.515")); 994 995 assert(Decimal("0.6") + 0.6 == Decimal("1.2")); 996 assert(0.6 + Decimal("0.6") == Decimal("1.2")); 997 assert(Decimal("1.5") - 1.7 == Decimal("-0.2")); 998 assert(1.5 - Decimal(1.7) == Decimal("-0.2")); 999 } 1000 1001 // += 1002 unittest { 1003 auto x = Decimal(5); 1004 auto y = Decimal(7); 1005 x += y; 1006 assert(x == Decimal(12)); 1007 assert(y == Decimal(7)); // no effects 1008 1009 x = Decimal("-0.1"); 1010 y = Decimal("2.05"); 1011 y += x; 1012 assert(y == Decimal("1.95")); 1013 assert(x == Decimal("-0.1")); 1014 1015 x = Decimal("4.12345", 5); 1016 y = Decimal("0.0000001"); 1017 x += y; 1018 assert(x == Decimal("4.12345")); // no effects 1019 assert(x.fixedPoint == 5); // no effects 1020 1021 x = Decimal("4.12345", 5); 1022 y = Decimal("0.000009"); 1023 x += y; 1024 assert(x == Decimal("4.12346")); 1025 assert(x.fixedPoint == 5); // no effects 1026 1027 // other data types 1028 x = Decimal(7); 1029 x += 2; 1030 assert(x == Decimal(9)); 1031 x -= 1.7; 1032 assert(x == Decimal(7.3)); 1033 x += "11.1"; 1034 assert(x == Decimal(18.4)); 1035 } 1036 1037 // -= 1038 unittest { 1039 auto x = Decimal(5); 1040 auto y = Decimal(7); 1041 x -= y; 1042 assert(x == Decimal(-2)); 1043 assert(y == Decimal(7)); // no effects 1044 1045 x = Decimal("-0.1"); 1046 y = Decimal("2.05"); 1047 y -= x; 1048 assert(y == Decimal("2.15")); 1049 assert(x == Decimal("-0.1")); // no effects 1050 1051 x = Decimal("4.12345", 5); 1052 y = Decimal("0.0000001"); 1053 x -= y; 1054 assert(x == Decimal("4.12345")); // no effects 1055 assert(x.fixedPoint == 5); // no effects 1056 assert(y.fixedPoint == defaultFixedPoint); // no effects 1057 1058 x = Decimal("4.12345", 5); 1059 y = Decimal("0.000009"); 1060 x -= y; 1061 assert(x == Decimal("4.12344")); 1062 assert(x.fixedPoint == 5); // no effects 1063 } 1064 1065 //mutliplication 1066 unittest { 1067 auto x = 3.D; 1068 auto y = 2.D; 1069 assert(x*y == 6.D); 1070 x = 2809.9324.D; 1071 y = 2.D; 1072 assert(x*y == "5619.8648".D); 1073 y = 2.7.D; 1074 assert(x*y == "7586,81748".D); 1075 y = 2.75.D; 1076 assert(x*y == "7727.3141".D); 1077 y = 2.05.D; 1078 assert(x*y == "5760.36142".D); 1079 x = "3.08".D; 1080 y = -2.D; 1081 assert(x*y == "-6.16".D); 1082 auto pi = "3.141592653589793238".D; 1083 assert(pi*pi == "9.869604401089358616".D); 1084 auto e = "2.718281828459045".D; 1085 assert(e*e == "7.389056098930648948".D); 1086 x = "0.3".D; 1087 y = 10.D; 1088 assert(x*y == 3.D); 1089 assert(100.D == 10.D * 10.D); 1090 assert(0.01.D == 0.1.D * 0.1.D); 1091 assert(0.001.D == "0.01".D * "0.1".D); 1092 assert(0.001.D == "0.1".D * "0.01".D); 1093 1094 assert(2.5.D * 2 == 5.D); 1095 assert(2 * 2.5.D * 2 == 10.D); 1096 1097 // check rounding 1098 x = Decimal("0.1", 2); 1099 y = Decimal("2.05", 2); 1100 auto z = x * y; 1101 assert(z == Decimal("0.21", 2)); 1102 x = Decimal("-0.1", 2); 1103 y = Decimal("2.05", 2); 1104 z = x * y; 1105 assert(z == Decimal("-0.21", 2)); 1106 } 1107 1108 //division 1109 unittest { 1110 assert(5.D / 2.D == 2.5.D); 1111 assert(5 / 2.D == 2.5.D); 1112 assert(5.D / 2 == 2.5.D); 1113 assert(0.D / 2.D == 0.D); 1114 assert("-13.50".D / "2.25".D == "-6".D); 1115 assert(Decimal("-13.50", 10) / Decimal("2.25", 4) == "-6".D); 1116 assert(1.D == 1.D/BigInt(1)); 1117 assert(1.D == Decimal(1, 10)/BigInt(1)); 1118 assert(1.D == Decimal(1, 25)/BigInt(1)); 1119 assert(0.5.D == 1.D/BigInt(2)); 1120 1121 auto x = Decimal(5); 1122 auto y = Decimal(7); 1123 assert(x/y == Decimal("0.714285714285714286")); 1124 } 1125 1126 // *= 1127 unittest { 1128 auto x = Decimal(5); 1129 auto y = Decimal(7); 1130 x *= y; 1131 assert(x == Decimal(35)); 1132 assert(y == Decimal(7)); // no effects 1133 1134 x = Decimal("-0.1"); 1135 y = Decimal("2.05"); 1136 y *= x; 1137 assert(y == Decimal("-0.205")); 1138 assert(x == Decimal("-0.1")); // no effects 1139 1140 x = Decimal("4.12345", 5); 1141 y = Decimal("0.0000001"); 1142 x *= y; 1143 assert(x == Decimal(0)); 1144 assert(x.fixedPoint == 5); // no effects 1145 assert(y.fixedPoint == defaultFixedPoint); // no effects 1146 1147 x = Decimal("4.12345", 5); 1148 y = Decimal("0.000009"); 1149 x *= y; 1150 assert(x == Decimal(0.00004)); 1151 assert(x.fixedPoint == 5); // no effects 1152 1153 x = Decimal("-0.1"); 1154 y = Decimal("2.05"); 1155 y *= x; 1156 assert(y == Decimal("-0.205")); 1157 assert(x == Decimal("-0.1")); // no effects 1158 1159 // check rounding 1160 x = Decimal("0.1", 2); 1161 y = Decimal("2.05", 2); 1162 y *= x; 1163 assert(y == Decimal("0.21", 2)); 1164 assert(x == Decimal("0.1", 2)); // no effects 1165 x = Decimal("-0.1", 2); 1166 y = Decimal("2.05", 2); 1167 y *= x; 1168 assert(y == Decimal("-0.21", 2)); 1169 assert(x == Decimal("-0.1", 2)); // no effects 1170 1171 x = Decimal("4.12345", 5); 1172 y = Decimal("0.0000001"); 1173 x *= y; 1174 assert(x == Decimal(0)); 1175 assert(x.fixedPoint == 5); // no effects 1176 assert(y.fixedPoint == defaultFixedPoint); // no effects 1177 1178 x = Decimal("4.12345", 5); 1179 y = Decimal("0.000009"); 1180 x *= y; 1181 assert(x == Decimal(0.00004)); 1182 assert(x.fixedPoint == 5); // no effects 1183 } 1184 1185 // /= 1186 unittest { 1187 auto x = Decimal(5); 1188 auto y = Decimal(7); 1189 x /= y; 1190 assert(x == Decimal("0.714285714285714286")); 1191 assert(y == Decimal(7)); // no effects 1192 1193 x = Decimal(-5); 1194 y = Decimal(7); 1195 x /= y; 1196 assert(x == Decimal("-0.714285714285714286")); 1197 assert(y == Decimal(7)); // no effects 1198 1199 } 1200 1201 //abs, round 1202 unittest { 1203 auto x = Decimal("-0.725"); 1204 assert(x.abs == Decimal("0.725")); 1205 x = Decimal("4.9"); 1206 assert(x.abs == Decimal("4.9")); 1207 1208 x = Decimal("43.5"); 1209 assert(x.round() == Decimal("44")); 1210 x = Decimal("-35.1"); 1211 assert(x.round() == Decimal("-35")); 1212 x = Decimal("-35.9"); 1213 assert(x.round() == Decimal("-36")); 1214 1215 x = Decimal("77.383838"); 1216 auto rounded = x.round(); 1217 assert(rounded == Decimal("77")); 1218 assert(rounded.fixedPoint == Decimal(x.fixedPoint)); 1219 rounded = x.round(1); 1220 assert(rounded == Decimal("77.4")); 1221 assert(rounded.fixedPoint == Decimal(x.fixedPoint)); 1222 rounded = x.round(2); 1223 assert(rounded == Decimal("77.38")); 1224 assert(rounded.fixedPoint == Decimal(x.fixedPoint)); 1225 rounded = x.round(3); 1226 assert(rounded == Decimal("77.384")); 1227 assert(rounded.fixedPoint == Decimal(x.fixedPoint)); 1228 x = Decimal("-77.383838"); 1229 rounded = x.round(3); 1230 assert(rounded == Decimal("-77.384")); 1231 assert(rounded.fixedPoint == Decimal(x.fixedPoint)); 1232 } 1233 1234 //exp 1235 unittest { 1236 string e = "2.71828182845904523536028747135266249775724709369995"; 1237 assert(exp(Decimal(1, 18)).toFullString() == e[0 .. 18+2]); 1238 assert(exp(Decimal(0.5, 6)).toFullString() == "1.648721"); 1239 assert(exp(Decimal(1, 3)) == Decimal(2.718)); 1240 assert(exp(Decimal(1, 2)) == Decimal(2.72)); 1241 assert(exp(Decimal(1, 1)) == Decimal(2.7)); 1242 assert(exp(Decimal(1, 0)) == Decimal(3)); 1243 } 1244 1245 //log and ln 1246 unittest { 1247 string e = "2.71828182845904523536028747135266249775724709369995"; 1248 assert(ln(Decimal(e)).toFullString == "1.000000000000000000"); 1249 assert(ln(Decimal(3)).toFullString == "1.098612288668109691"); 1250 assert(ln(Decimal(3, 9)) == "1.098612289".D); 1251 assert(ln(Decimal(3, 5)).toFullString == "1.09861"); 1252 assert(ln(Decimal(3, 4)).toFullString == "1.0986"); 1253 assert(ln(Decimal(3, 3)).toFullString == "1.099"); 1254 assert(ln(Decimal(3, 2)).toFullString == "1.10"); 1255 assert(ln(Decimal(3, 1)).toFullString == "1.1"); 1256 assert(ln(Decimal(3, 0)).toFullString == "1"); 1257 assert(format!"%.8D"(ln(Decimal(e, 5))) == "1.00000000"); 1258 assert(format!"%.8D"(ln(Decimal(e, 1))) == "1.00000000"); 1259 1260 assert(log(100.D, 10.D) == 2.D); 1261 } 1262 1263 //pow 1264 unittest { 1265 auto x = Decimal(2); 1266 assert(pow(x, 2) == 4.D); 1267 assert(pow(3.D, 3) == 27.D); 1268 assert(pow(2.D, 0.1.D) == "1.071773462536293164".D); 1269 auto pi = Decimal( 1270 "3.1415926535897932384626433832795028841971693993751" ~ 1271 "05820974944592307816406286208998628034825342117", 1272 96 //precision 1273 ); 1274 auto piSquared = pow(pi, 2); 1275 auto expectedResult = Decimal( 1276 "9.86960440108935861883449099987615113531369940724" ~ 1277 "0790626413349376220044822419205243001773403718552", 1278 96 1279 ); 1280 assert(piSquared == expectedResult); 1281 assert(piSquared.fixedPoint == expectedResult.fixedPoint); 1282 1283 //overloading ^^ 1284 assert(2.D ^^ 3.D == 8.D); 1285 assert(2.D ^^ 3 == 8.D); 1286 assert(2 ^^ 3.D == 8.D); 1287 assert(Decimal(2.0, 2) ^^ Decimal(0.1, 18) == "1.071773462536293164".D); 1288 assert(Decimal(2.0, 2) ^^ Decimal(0.1, 16) == "1.0717734625362932".D); 1289 assert(2.D ^^ 0.1.D == "1.071773462536293164".D); 1290 assert(2.D ^^ 0.1 == "1.071773462536293164".D); 1291 assert(2.0 ^^ 0.1.D == "1.071773462536293164".D); 1292 1293 auto a = Decimal(4.0, 2); 1294 auto b = Decimal(2.0, 3); 1295 auto z = a ^^ b; 1296 assert(z.fixedPoint == 3); 1297 assert(z == Decimal(16)); 1298 1299 a = Decimal(4, 0); 1300 b = Decimal(2, 0); 1301 z = a ^^ b; 1302 assert(z.fixedPoint == 0); 1303 assert(z == Decimal(16)); 1304 1305 a = Decimal(4.5, 1); 1306 b = Decimal(5.25, 1); 1307 z = a ^^ b; 1308 assert(z == Decimal("2492.9")); 1309 1310 a = Decimal(4.5, 1); 1311 b = Decimal(5.25, 2); 1312 z = a ^^ b; 1313 assert(z == Decimal(2687.61)); 1314 1315 // ^^= 1316 a = Decimal(4.5, 2); 1317 b = Decimal(5.25, 2); 1318 a ^^= b; 1319 assert(a == Decimal(2687.61)); 1320 1321 a = Decimal(4.5, 3); 1322 b = Decimal(5.25, 3); 1323 a ^^= b; 1324 assert(a == Decimal("2687.607")); 1325 } 1326