1 /++ 2 Stack-allocated decimal type. 3 4 Note: 5 The module doesn't provide full arithmetic API for now. 6 +/ 7 module mir.bignum.decimal; 8 9 import mir.serde: serdeProxy, serdeScoped; 10 import std.traits: isSomeChar; 11 public import mir.bignum.low_level_view: DecimalExponentKey; 12 import mir.bignum.low_level_view: ceilLog10Exp2; 13 14 private enum expBufferLength = 2 + ceilLog10Exp2(size_t.sizeof * 8); 15 private static immutable C[9] zerosImpl(C) = "0.00000.0"; 16 17 /++ 18 Stack-allocated decimal type. 19 Params: 20 maxSize64 = count of 64bit words in coefficient 21 +/ 22 @serdeScoped @serdeProxy!(const(char)[]) 23 struct Decimal(size_t maxSize64) 24 if (maxSize64 && maxSize64 <= ushort.max) 25 { 26 import mir.format: NumericSpec; 27 import mir.bignum.integer; 28 import mir.bignum.low_level_view; 29 import std.traits: isMutable, isFloatingPoint; 30 31 /// 32 sizediff_t exponent; 33 /// 34 BigInt!maxSize64 coefficient; 35 36 /// 37 DecimalView!size_t view() 38 { 39 return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); 40 } 41 42 /// ditto 43 DecimalView!(const size_t) view() const 44 { 45 return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); 46 } 47 48 /// 49 this(C)(scope const(C)[] str, int exponentShift = 0) @safe pure @nogc 50 if (isSomeChar!C) 51 { 52 DecimalExponentKey key; 53 if (fromStringImpl(str, key, exponentShift) || key == DecimalExponentKey.nan || key == DecimalExponentKey.infinity) 54 return; 55 static if (__traits(compiles, () @nogc { throw new Exception("Can't parse Decimal."); })) 56 { 57 import mir.exception: MirException; 58 throw new MirException("Can't parse Decimal!" ~ maxSize64.stringof ~ " from string `", str , "`"); 59 } 60 else 61 { 62 static immutable exception = new Exception("Can't parse Decimal!" ~ maxSize64.stringof ~ "."); 63 throw exception; 64 } 65 } 66 67 static if (maxSize64 == 3) 68 /// 69 version(mir_test) @safe pure @nogc unittest 70 { 71 import mir.math.constant: PI; 72 Decimal!2 decimal = "3.141592653589793378e-40"; // constructor 73 assert(cast(double) decimal == double(PI) / 1e40); 74 } 75 76 /++ 77 Constructs Decimal from the floating point number using the $(HTTPS github.com/ulfjack/ryu, Ryu algorithm). 78 79 The number is the shortest decimal representation that being converted back would result the same floating-point number. 80 +/ 81 this(T)(const T x) 82 if (isFloatingPoint!T && maxSize64 >= 1 + (T.mant_dig >= 64)) 83 { 84 import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; 85 this = genericBinaryToDecimal(x); 86 } 87 88 static if (maxSize64 == 3) 89 /// 90 version(mir_bignum_test) 91 @safe pure nothrow @nogc 92 unittest 93 { 94 // float and double can be used to construct Decimal of any length 95 auto decimal64 = Decimal!1(-1.235e-7); 96 assert(decimal64.exponent == -10); 97 assert(decimal64.coefficient == -1235); 98 99 // real number may need Decimal at least length of 2 100 auto decimal128 = Decimal!2(-1.235e-7L); 101 assert(decimal128.exponent == -10); 102 assert(decimal128.coefficient == -1235); 103 104 decimal128 = Decimal!2(1234e3f); 105 assert(decimal128.exponent == 3); 106 assert(decimal128.coefficient == 1234); 107 } 108 109 /// 110 ref opAssign(size_t rhsMaxSize64)(auto ref scope const Decimal!rhsMaxSize64 rhs) return 111 if (rhsMaxSize64 < maxSize64) 112 { 113 this.exponent = rhs.exponent; 114 this.coefficient = rhs.coefficient; 115 return this; 116 } 117 118 /++ 119 Handle thousand separators for non exponential numbers. 120 121 Returns: false in case of overflow or incorrect string. 122 +/ 123 bool fromStringWithThousandsSeparatorImpl(C, 124 bool allowSpecialValues = true, 125 bool allowStartingPlus = true, 126 bool allowLeadingZeros = true, 127 )( 128 scope const(C)[] str, 129 const C thousandsSeparator, 130 const C fractionSeparator, 131 out DecimalExponentKey key, 132 int exponentShift = 0, 133 ) 134 if (isSomeChar!C) 135 { 136 import mir.algorithm.iteration: find; 137 import mir.format: stringBuf; 138 import mir.ndslice.chunks: chunks; 139 import mir.ndslice.slice: sliced; 140 import mir.ndslice.topology: retro; 141 142 stringBuf buffer; 143 assert(thousandsSeparator != fractionSeparator); 144 if (str.length && (str[0] == '+' || str[0] == '-')) 145 { 146 buffer.put(cast(char)str[0]); 147 str = str[1 .. $]; 148 } 149 auto integer = str[0 .. $ - str.find!(a => a == fractionSeparator)]; 150 if (integer.length % 4 == 0) 151 return false; 152 foreach_reverse (chunk; integer.sliced.retro.chunks(4)) 153 { 154 auto s = chunk.retro.field; 155 if (s.length == 4) 156 { 157 if (s[0] != thousandsSeparator) 158 return false; 159 s = s[1 .. $]; 160 } 161 do 162 { 163 if (s[0] < '0' || s[0] > '9') 164 return false; 165 buffer.put(cast(char)s[0]); 166 s = s[1 .. $]; 167 } 168 while(s.length); 169 } 170 if (str.length > integer.length) 171 { 172 buffer.put('.'); 173 str = str[integer.length + 1 .. $]; 174 if (str.length == 0) 175 return false; 176 do 177 { 178 buffer.put(cast(char)str[0]); 179 str = str[1 .. $]; 180 } 181 while(str.length); 182 } 183 return fromStringImpl!(char, 184 allowSpecialValues, 185 false, // allowDotOnBounds 186 false, // allowDExponent 187 allowStartingPlus, 188 false, // allowUnderscores 189 allowLeadingZeros, // allowLeadingZeros 190 false, // allowExponent 191 false, // checkEmpty 192 )(buffer.data, key, exponentShift); 193 } 194 195 static if (maxSize64 == 3) 196 /// 197 version(mir_bignum_test) 198 @safe pure nothrow @nogc 199 unittest 200 { 201 Decimal!3 decimal; 202 DecimalExponentKey key; 203 204 assert(decimal.fromStringWithThousandsSeparatorImpl("12,345.678", ',', '.', key)); 205 assert(cast(double) decimal == 12345.678); 206 assert(key == DecimalExponentKey.dot); 207 208 assert(decimal.fromStringWithThousandsSeparatorImpl("12,345,678", ',', '.', key, -3)); 209 assert(cast(double) decimal == 12345.678); 210 assert(key == DecimalExponentKey.none); 211 212 assert(decimal.fromStringWithThousandsSeparatorImpl("021 345,678", ' ', ',', key)); 213 assert(cast(double) decimal == 21345.678); 214 assert(key == DecimalExponentKey.dot); 215 } 216 217 /++ 218 Returns: false in case of overflow or incorrect string. 219 +/ 220 bool fromStringImpl(C, 221 bool allowSpecialValues = true, 222 bool allowDotOnBounds = true, 223 bool allowDExponent = true, 224 bool allowStartingPlus = true, 225 bool allowUnderscores = true, 226 bool allowLeadingZeros = true, 227 bool allowExponent = true, 228 bool checkEmpty = true, 229 ) 230 (scope const(C)[] str, out DecimalExponentKey key, int exponentShift = 0) 231 scope @trusted pure @nogc nothrow 232 if (isSomeChar!C) 233 { 234 enum optimize = size_t.sizeof == 8 && maxSize64 == 1; 235 version(LDC) 236 { 237 static if (optimize || (allowSpecialValues && allowDExponent && allowStartingPlus && checkEmpty) == false) 238 pragma(inline, true); 239 } 240 static if (optimize) 241 { 242 import mir.utility: _expect; 243 static if (checkEmpty) 244 { 245 if (_expect(str.length == 0, false)) 246 return false; 247 } 248 249 coefficient.sign = str[0] == '-'; 250 if (coefficient.sign) 251 { 252 str = str[1 .. $]; 253 if (_expect(str.length == 0, false)) 254 return false; 255 } 256 else 257 static if (allowStartingPlus) 258 { 259 if (_expect(str[0] == '+', false)) 260 { 261 str = str[1 .. $]; 262 if (_expect(str.length == 0, false)) 263 return false; 264 } 265 } 266 267 uint d = str[0] - '0'; 268 str = str[1 .. $]; 269 exponent = 0; 270 271 ulong v; 272 bool dot; 273 static if (allowUnderscores) 274 { 275 bool recentUnderscore; 276 } 277 static if (!allowLeadingZeros) 278 { 279 if (d == 0) 280 { 281 if (str.length == 0) 282 goto R; 283 if (str[0] >= '0' && str[0] <= '9') 284 return false; 285 goto S; 286 } 287 } 288 289 if (d < 10) 290 { 291 goto S; 292 } 293 294 static if (allowDotOnBounds) 295 { 296 if (d == '.' - '0') 297 { 298 if (str.length == 0) 299 return false; 300 key = DecimalExponentKey.dot; 301 dot = true; 302 goto F; 303 } 304 } 305 306 static if (allowSpecialValues) 307 { 308 goto NI; 309 } 310 else 311 { 312 return false; 313 } 314 315 F: for(;;) 316 { 317 d = str[0] - '0'; 318 str = str[1 .. $]; 319 320 if (_expect(d <= 10, true)) 321 { 322 static if (allowUnderscores) 323 { 324 recentUnderscore = false; 325 } 326 { 327 import mir.checkedint: mulu; 328 bool overflow; 329 v = mulu(v, cast(uint)10, overflow); 330 if (overflow) 331 break; 332 } 333 S: 334 v += d; 335 exponentShift -= dot; 336 if (str.length) 337 continue; 338 E: 339 exponent += exponentShift; 340 R: 341 coefficient.data[0] = v; 342 coefficient.length = v != 0; 343 static if (allowUnderscores) 344 { 345 return !recentUnderscore; 346 } 347 else 348 { 349 return true; 350 } 351 } 352 static if (allowUnderscores) 353 { 354 if (recentUnderscore) 355 return false; 356 } 357 switch (d) 358 { 359 case DecimalExponentKey.dot: 360 key = DecimalExponentKey.dot; 361 if (_expect(dot, false)) 362 break; 363 dot = true; 364 if (str.length) 365 { 366 static if (allowUnderscores) 367 { 368 recentUnderscore = true; 369 } 370 continue; 371 } 372 static if (allowDotOnBounds) 373 { 374 goto R; 375 } 376 else 377 { 378 break; 379 } 380 static if (allowExponent) 381 { 382 static if (allowDExponent) 383 { 384 case DecimalExponentKey.d: 385 case DecimalExponentKey.D: 386 goto case DecimalExponentKey.e; 387 } 388 case DecimalExponentKey.e: 389 case DecimalExponentKey.E: 390 import mir.parse: parse; 391 key = cast(DecimalExponentKey)d; 392 if (parse(str, exponent) && str.length == 0) 393 goto E; 394 break; 395 } 396 static if (allowUnderscores) 397 { 398 case '_' - '0': 399 recentUnderscore = true; 400 if (str.length) 401 continue; 402 break; 403 } 404 default: 405 } 406 break; 407 } 408 return false; 409 static if (allowSpecialValues) 410 { 411 NI: 412 exponent = exponent.max; 413 if (str.length == 2) 414 { 415 auto stail = cast(C[2])str[0 .. 2]; 416 if (d == 'i' - '0' && stail == cast(C[2])"nf" || d == 'I' - '0' && (stail == cast(C[2])"nf" || stail == cast(C[2])"NF")) 417 { 418 key = DecimalExponentKey.infinity; 419 goto R; 420 } 421 if (d == 'n' - '0' && stail == cast(C[2])"an" || d == 'N' - '0' && (stail == cast(C[2])"aN" || stail == cast(C[2])"AN")) 422 { 423 v = 1; 424 key = DecimalExponentKey.nan; 425 goto R; 426 } 427 } 428 return false; 429 } 430 } 431 else 432 { 433 import mir.bignum.low_level_view: DecimalView, BigUIntView, MaxWordPow10; 434 auto work = DecimalView!size_t(false, 0, BigUIntView!size_t(coefficient.data)); 435 auto ret = work.fromStringImpl!(C, 436 allowSpecialValues, 437 allowDotOnBounds, 438 allowDExponent, 439 allowStartingPlus, 440 allowUnderscores, 441 allowLeadingZeros, 442 allowExponent, 443 checkEmpty, 444 )(str, key, exponentShift); 445 coefficient.length = cast(uint) work.coefficient.coefficients.length; 446 coefficient.sign = work.sign; 447 exponent = work.exponent; 448 return ret; 449 } 450 } 451 452 static if (maxSize64 == 3) 453 /// 454 version(mir_bignum_test) 455 @safe pure nothrow @nogc 456 unittest 457 { 458 import mir.conv: to; 459 Decimal!3 decimal; 460 DecimalExponentKey key; 461 462 // Check precise percentate parsing 463 assert(decimal.fromStringImpl("71.7", key, -2)); 464 assert(key == DecimalExponentKey.dot); 465 // The result is exact value instead of 0.7170000000000001 = 71.7 / 100 466 assert(cast(double) decimal == 0.717); 467 468 assert(decimal.fromStringImpl("+0.334e-5"w, key)); 469 assert(key == DecimalExponentKey.e); 470 assert(cast(double) decimal == 0.334e-5); 471 472 assert(decimal.fromStringImpl("100_000_000"w, key)); 473 assert(key == DecimalExponentKey.none); 474 assert(cast(double) decimal == 1e8); 475 476 assert(decimal.fromStringImpl("-334D-5"d, key)); 477 assert(key == DecimalExponentKey.D); 478 assert(cast(double) decimal == -334e-5); 479 480 assert(decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); 481 assert(key == DecimalExponentKey.none); 482 assert(cast(double) decimal == 2482734692817364218734682973648217364981273648923423.0); 483 484 assert(decimal.fromStringImpl(".023", key)); 485 assert(key == DecimalExponentKey.dot); 486 assert(cast(double) decimal == .023); 487 488 assert(decimal.fromStringImpl("0E100", key)); 489 assert(key == DecimalExponentKey.E); 490 assert(cast(double) decimal == 0); 491 492 foreach (str; ["-nan", "-NaN", "-NAN"]) 493 { 494 assert(decimal.fromStringImpl(str, key)); 495 assert(decimal.coefficient.length > 0); 496 assert(decimal.exponent == decimal.exponent.max); 497 assert(decimal.coefficient.sign); 498 assert(key == DecimalExponentKey.nan); 499 assert(cast(double) decimal != cast(double) decimal); 500 } 501 502 foreach (str; ["inf", "Inf", "INF"]) 503 { 504 assert(decimal.fromStringImpl(str, key)); 505 assert(decimal.coefficient.length == 0); 506 assert(decimal.exponent == decimal.exponent.max); 507 assert(key == DecimalExponentKey.infinity); 508 assert(cast(double) decimal == double.infinity); 509 } 510 511 assert(decimal.fromStringImpl("-inf", key)); 512 assert(decimal.coefficient.length == 0); 513 assert(decimal.exponent == decimal.exponent.max); 514 assert(key == DecimalExponentKey.infinity); 515 assert(cast(double) decimal == -double.infinity); 516 517 assert(!decimal.fromStringImpl("3.3.4", key)); 518 assert(!decimal.fromStringImpl("3.4.", key)); 519 assert(decimal.fromStringImpl("4.", key)); 520 assert(!decimal.fromStringImpl(".", key)); 521 assert(decimal.fromStringImpl("0.", key)); 522 assert(decimal.fromStringImpl("00", key)); 523 assert(!decimal.fromStringImpl("0d", key)); 524 } 525 526 static if (maxSize64 == 3) 527 version(mir_bignum_test) 528 @safe pure nothrow @nogc 529 unittest 530 { 531 import mir.conv: to; 532 Decimal!1 decimal; 533 DecimalExponentKey key; 534 535 assert(decimal.fromStringImpl("1.334", key)); 536 assert(key == DecimalExponentKey.dot); 537 assert(cast(double) decimal == 1.334); 538 539 assert(decimal.fromStringImpl("+0.334e-5"w, key)); 540 assert(key == DecimalExponentKey.e); 541 assert(cast(double) decimal == 0.334e-5); 542 543 assert(decimal.fromStringImpl("-334D-5"d, key)); 544 assert(key == DecimalExponentKey.D); 545 assert(cast(double) decimal == -334e-5); 546 547 assert(!decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); 548 549 assert(decimal.fromStringImpl(".023", key)); 550 assert(key == DecimalExponentKey.dot); 551 assert(cast(double) decimal == .023); 552 553 assert(decimal.fromStringImpl("0E100", key)); 554 assert(key == DecimalExponentKey.E); 555 assert(cast(double) decimal == 0); 556 557 foreach (str; ["-nan", "-NaN", "-NAN"]) 558 { 559 assert(decimal.fromStringImpl(str, key)); 560 assert(decimal.coefficient.length > 0); 561 assert(decimal.exponent == decimal.exponent.max); 562 assert(decimal.coefficient.sign); 563 assert(key == DecimalExponentKey.nan); 564 assert(cast(double) decimal != cast(double) decimal); 565 } 566 567 foreach (str; ["inf", "Inf", "INF"]) 568 { 569 assert(decimal.fromStringImpl(str, key)); 570 assert(decimal.coefficient.length == 0); 571 assert(decimal.exponent == decimal.exponent.max); 572 assert(key == DecimalExponentKey.infinity); 573 assert(cast(double) decimal == double.infinity); 574 } 575 576 assert(decimal.fromStringImpl("-inf", key)); 577 assert(decimal.coefficient.length == 0); 578 assert(decimal.exponent == decimal.exponent.max); 579 assert(key == DecimalExponentKey.infinity); 580 assert(cast(double) decimal == -double.infinity); 581 582 assert(!decimal.fromStringImpl("3.3.4", key)); 583 assert(!decimal.fromStringImpl("3.4.", key)); 584 assert(decimal.fromStringImpl("4.", key)); 585 assert(!decimal.fromStringImpl(".", key)); 586 assert(decimal.fromStringImpl("0.", key)); 587 assert(decimal.fromStringImpl("00", key)); 588 assert(!decimal.fromStringImpl("0d", key)); 589 } 590 591 private enum coefficientBufferLength = 2 + ceilLog10Exp2(coefficient.data.length * (size_t.sizeof * 8)); // including dot and sign 592 private enum eDecimalLength = coefficientBufferLength + expBufferLength; 593 594 /// 595 immutable(C)[] toString(C = char)(NumericSpec spec = NumericSpec.init) const @safe pure nothrow 596 if(isSomeChar!C && isMutable!C) 597 { 598 import mir.appender: UnsafeArrayBuffer; 599 C[eDecimalLength] data = void; 600 auto buffer = UnsafeArrayBuffer!C(data); 601 toString(buffer, spec); 602 return buffer.data.idup; 603 } 604 605 static if (maxSize64 == 3) 606 /// 607 version(mir_bignum_test) @safe pure unittest 608 { 609 auto str = "-3.4010447314490204552169750449563978034784726557588085989975288830070948234680e-13245"; 610 auto decimal = Decimal!4(str); 611 assert(decimal.toString == str, decimal.toString); 612 613 decimal = Decimal!4.init; 614 assert(decimal.toString == "0.0"); 615 } 616 617 /// 618 void toString(C = char, W)(scope ref W w, NumericSpec spec = NumericSpec.init) const 619 if(isSomeChar!C && isMutable!C) 620 { 621 assert(spec.format == NumericSpec.Format.exponent || spec.format == NumericSpec.Format.human); 622 import mir.utility: _expect; 623 // handle special values 624 if (_expect(exponent == exponent.max, false)) 625 { 626 static immutable C[3] nan = "nan"; 627 static immutable C[4] ninf = "-inf"; 628 static immutable C[4] pinf = "+inf"; 629 w.put(coefficient.length == 0 ? coefficient.sign ? ninf[] : pinf[] : nan[]); 630 return; 631 } 632 633 C[coefficientBufferLength + 16] buffer0 = void; 634 auto buffer = buffer0[0 .. $ - 16]; 635 636 size_t coefficientLength; 637 static if (size_t.sizeof == 8) 638 { 639 if (__ctfe) 640 { 641 uint[coefficient.data.length * 2] data; 642 foreach (i; 0 .. coefficient.length) 643 { 644 auto l = cast(uint)coefficient.data[i]; 645 auto h = cast(uint)(coefficient.data[i] >> 32); 646 version (LittleEndian) 647 { 648 data[i * 2 + 0] = l; 649 data[i * 2 + 1] = h; 650 } 651 else 652 { 653 data[$ - 1 - (i * 2 + 0)] = l; 654 data[$ - 1 - (i * 2 + 1)] = h; 655 } 656 } 657 auto work = BigUIntView!uint(data); 658 work = work.topLeastSignificantPart(coefficient.length * 2).normalized; 659 coefficientLength = work.toStringImpl(buffer); 660 } 661 else 662 { 663 BigInt!maxSize64 work = coefficient; 664 coefficientLength = work.view.unsigned.toStringImpl(buffer); 665 } 666 } 667 else 668 { 669 BigInt!maxSize64 work = coefficient; 670 coefficientLength = work.view.unsigned.toStringImpl(buffer); 671 } 672 673 C[1] sign = coefficient.sign ? "-" : "+"; 674 bool addSign = coefficient.sign || spec.plus; 675 sizediff_t s = this.exponent + coefficientLength; 676 677 alias zeros = zerosImpl!C; 678 679 if (spec.format == NumericSpec.Format.human) 680 { 681 if (!spec.separatorCount) 682 spec.separatorCount = 3; 683 void putL(scope const(C)[] b) 684 { 685 assert(b.length); 686 687 if (addSign) 688 w.put(sign[]); 689 690 auto r = b.length % spec.separatorCount; 691 if (r == 0) 692 r = spec.separatorCount; 693 C[1] sep = spec.separatorChar; 694 goto LS; 695 do 696 { 697 w.put(sep[]); 698 LS: 699 w.put(b[0 .. r]); 700 b = b[r .. $]; 701 r = spec.separatorCount; 702 } 703 while(b.length); 704 } 705 706 // try print decimal form without exponent 707 // up to 6 digits exluding leading 0. or final .0 708 if (s <= 0) 709 { 710 //0.001.... 711 //0.0001 712 //0.00001 713 //0.000001 714 //If separatorChar is defined lets be less greed for space. 715 if (this.exponent >= -6 || s >= -2 - (spec.separatorChar != 0) * 3) 716 { 717 if (addSign) 718 w.put(sign[]); 719 w.put(zeros[0 .. -s + 2]); 720 w.put(buffer[$ - coefficientLength .. $]); 721 return; 722 } 723 } 724 else 725 if (this.exponent >= 0) 726 { 727 ///dddddd.0 728 if (!spec.separatorChar) 729 { 730 if (s <= 6) 731 { 732 buffer[$ - coefficientLength - 1] = sign[0]; 733 w.put(buffer[$ - coefficientLength - addSign .. $]); 734 w.put(zeros[$ - (this.exponent + 2) .. $]); 735 return; 736 } 737 } 738 else 739 { 740 if (s <= 12) 741 { 742 buffer0[$ - 16 .. $] = '0'; 743 putL(buffer0[$ - coefficientLength - 16 .. $ - 16 + this.exponent]); 744 w.put(zeros[$ - 2 .. $]); 745 return; 746 } 747 } 748 } 749 else 750 { 751 ///dddddd.0 752 if (!spec.separatorChar) 753 { 754 ///dddddd.d.... 755 if (s <= 6 || coefficientLength <= 6) 756 { 757 buffer[$ - coefficientLength - 1] = sign[0]; 758 w.put(buffer[$ - coefficientLength - addSign .. $ - coefficientLength + s]); 759 T2: 760 buffer[$ - coefficientLength + s - 1] = '.'; 761 w.put(buffer[$ - coefficientLength + s - 1 .. $]); 762 return; 763 } 764 } 765 else 766 { 767 if (s <= 12 || coefficientLength <= 12) 768 { 769 putL(buffer[$ - coefficientLength .. $ - coefficientLength + s]); 770 goto T2; 771 } 772 } 773 } 774 } 775 776 assert(coefficientLength); 777 778 sizediff_t exponent = s - 1; 779 780 if (coefficientLength > 1) 781 { 782 auto c = buffer[$ - coefficientLength]; 783 buffer[$ - coefficientLength] = '.'; 784 buffer[$ - ++coefficientLength] = c; 785 } 786 787 buffer[$ - coefficientLength - 1] = sign[0]; 788 w.put(buffer[$ - coefficientLength - addSign .. $]); 789 790 import mir.format_impl: printSignedToTail; 791 792 static if (sizediff_t.sizeof == 8) 793 enum N = 21; 794 else 795 enum N = 11; 796 797 // prints e+/-exponent 798 auto expLength = printSignedToTail(exponent, buffer0[$ - N - 16 .. $ - 16], '+'); 799 buffer[$ - ++expLength] = spec.exponentChar; 800 w.put(buffer[$ - expLength .. $]); 801 } 802 803 static if (maxSize64 == 3) 804 /// Check @nogc toString impl 805 version(mir_bignum_test) @safe pure @nogc unittest 806 { 807 import mir.format: stringBuf; 808 auto str = "5.28238923728e-876543210"; 809 auto decimal = Decimal!1(str); 810 stringBuf buffer; 811 buffer << decimal; 812 assert(buffer.data == str, buffer.data); 813 } 814 815 /++ 816 Mir parsing supports up-to quadruple precision. The conversion error is 0 ULP for normal numbers. 817 Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP. +/ 818 T opCast(T, bool wordNormalized = false, bool nonZero = false)() const 819 if (isFloatingPoint!T && isMutable!T) 820 { 821 822 enum optimize = maxSize64 == 1 && size_t.sizeof == 8 && T.mant_dig < 64; 823 824 version(LDC) 825 { 826 static if (optimize || wordNormalized) 827 pragma(inline, true); 828 } 829 830 static if (optimize) 831 { 832 import mir.bignum.fixed: UInt; 833 import mir.bignum.fp: Fp, extendedMul; 834 import mir.bignum.internal.dec2flt_table; 835 import mir.bignum.low_level_view: MaxWordPow5, MaxFpPow5; 836 import mir.math.common: floor; 837 import mir.utility: _expect; 838 839 T ret = 0; 840 size_t length = coefficient.length; 841 842 843 static if (!wordNormalized) 844 { 845 if (coefficient.data[0] == 0) 846 length = 0; 847 } 848 849 if (_expect(exponent == exponent.max, false)) 850 { 851 ret = length ? T.nan : T.infinity; 852 goto R; 853 } 854 855 static if (!nonZero) 856 if (length == 0) 857 goto R; 858 enum S = 9; 859 860 Fp!64 load(typeof(exponent) e) 861 { 862 auto p10coeff = p10_coefficients[cast(sizediff_t)e - min_p10_e][0]; 863 auto p10exp = p10_exponents[cast(sizediff_t)e - min_p10_e]; 864 return Fp!64(false, p10exp, UInt!64(p10coeff)); 865 } 866 { 867 auto expSign = exponent < 0; 868 if (_expect((expSign ? -exponent : exponent) >>> S == 0, true)) 869 { 870 enum ulong mask = (1UL << (64 - T.mant_dig)) - 1; 871 enum ulong half = (1UL << (64 - T.mant_dig - 1)); 872 enum ulong bound = ulong(1) << T.mant_dig; 873 874 auto c = Fp!64(UInt!64(coefficient.data[0])); 875 auto z = c.extendedMul(load(exponent)); 876 ret = cast(T) z; 877 long bitsDiff = (cast(ulong) z.opCast!(Fp!64).coefficient & mask) - half; 878 if (_expect((bitsDiff < 0 ? -bitsDiff : bitsDiff) > 3 * expSign, true)) 879 goto R; 880 if (!expSign && exponent <= MaxWordPow5!ulong || exponent == 0) 881 goto R; 882 if (expSign && MaxFpPow5!T >= -exponent && cast(ulong)c.coefficient < bound) 883 { 884 auto e = load(-exponent); 885 ret = c.opCast!(T, true) / cast(T) (cast(ulong)e.coefficient >> e.exponent); 886 goto R; 887 } 888 ret = algoR!T(ret, view.coefficient, cast(int) exponent); 889 goto R; 890 } 891 ret = expSign ? 0 : T.infinity; 892 } 893 R: 894 if (coefficient.sign) 895 ret = -ret; 896 return ret; 897 } 898 else 899 { 900 return view.opCast!(T, wordNormalized, nonZero); 901 } 902 } 903 904 /// 905 bool isNaN() const @property 906 { 907 return exponent == exponent.max && coefficient.length; 908 } 909 910 /// 911 bool isInfinity() const @property 912 { 913 return exponent == exponent.max && !coefficient.length; 914 } 915 916 /// 917 ref opOpAssign(string op, size_t rhsMaxSize64)(ref const Decimal!rhsMaxSize64 rhs) @safe pure return 918 if (op == "+" || op == "-") 919 { 920 BigInt!rhsMaxSize64 rhsCopy; 921 BigIntView!(const size_t) rhsView; 922 auto expDiff = exponent - rhs.exponent; 923 if (expDiff >= 0) 924 { 925 exponent = rhs.exponent; 926 coefficient.mulPow5(expDiff); 927 coefficient.opOpAssign!"<<"(expDiff); 928 rhsView = rhs.coefficient.view; 929 } 930 else 931 { 932 rhsCopy.copyFrom(rhs.coefficient.view); 933 rhsCopy.mulPow5(-expDiff); 934 rhsCopy.opOpAssign!"<<"(-expDiff); 935 rhsView = rhsCopy.view; 936 } 937 coefficient.opOpAssign!op(rhsView); 938 return this; 939 } 940 941 static if (maxSize64 == 3) 942 /// 943 version(mir_bignum_test) @safe pure @nogc unittest 944 { 945 import std.stdio; 946 auto a = Decimal!1("777.7"); 947 auto b = Decimal!1("777"); 948 import mir.format; 949 assert(stringBuf() << cast(double)a - cast(double)b << getData == "0.7000000000000455"); 950 a -= b; 951 assert(stringBuf() << a << getData == "0.7"); 952 953 a = Decimal!1("-777.7"); 954 b = Decimal!1("777"); 955 a += b; 956 assert(stringBuf() << a << getData == "-0.7"); 957 958 a = Decimal!1("777.7"); 959 b = Decimal!1("-777"); 960 a += b; 961 assert(stringBuf() << a << getData == "0.7"); 962 963 a = Decimal!1("777"); 964 b = Decimal!1("777.7"); 965 a -= b; 966 assert(stringBuf() << a << getData == "-0.7"); 967 } 968 } 969 970 /// 971 version(mir_bignum_test) 972 @safe pure nothrow @nogc 973 unittest 974 { 975 import mir.conv: to; 976 Decimal!3 decimal; 977 DecimalExponentKey key; 978 979 import mir.math.constant: PI; 980 assert(decimal.fromStringImpl("3.141592653589793378e-10", key)); 981 assert(cast(double) decimal == double(PI) / 1e10); 982 assert(key == DecimalExponentKey.e); 983 } 984 985 986 /// 987 version(mir_bignum_test) 988 @safe pure nothrow @nogc 989 unittest 990 { 991 import mir.conv: to; 992 Decimal!3 decimal; 993 DecimalExponentKey key; 994 995 assert(decimal.fromStringImpl("0", key)); 996 assert(key == DecimalExponentKey.none); 997 assert(decimal.exponent == 0); 998 assert(decimal.coefficient.length == 0); 999 assert(!decimal.coefficient.sign); 1000 assert(cast(double) decimal.coefficient == 0); 1001 1002 assert(decimal.fromStringImpl("-0.0", key)); 1003 assert(key == DecimalExponentKey.dot); 1004 assert(decimal.exponent == -1); 1005 assert(decimal.coefficient.length == 0); 1006 assert(decimal.coefficient.sign); 1007 assert(cast(double) decimal.coefficient == 0); 1008 1009 assert(decimal.fromStringImpl("0e0", key)); 1010 assert(key == DecimalExponentKey.e); 1011 assert(decimal.exponent == 0); 1012 assert(decimal.coefficient.length == 0); 1013 assert(!decimal.coefficient.sign); 1014 assert(cast(double) decimal.coefficient == 0); 1015 } 1016 1017 deprecated("use decimal.fromStringImpl insteade") 1018 @trusted @nogc pure nothrow 1019 bool parseDecimal(size_t maxSize64, C)(scope const(C)[] str, ref Decimal!maxSize64 decimal, out DecimalExponentKey key) 1020 if (isSomeChar!C) 1021 { 1022 return decimal.fromStringImpl(str, key); 1023 } 1024 1025 1026 version(mir_bignum_test) 1027 @safe pure @nogc unittest 1028 { 1029 Decimal!4 i = "-0"; 1030 1031 assert(i.view.coefficient.coefficients.length == 0); 1032 assert(i.coefficient.view.unsigned.coefficients.length == 0); 1033 assert(i.coefficient.view == 0L); 1034 assert(cast(long) i.coefficient == 0); 1035 }