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 /// 12 public import mir.parse: DecimalExponentKey; 13 import mir.bignum.low_level_view: ceilLog10Exp2; 14 15 private enum expBufferLength = 2 + ceilLog10Exp2(ulong.sizeof * 8); 16 private static immutable C[9] zerosImpl(C) = "0.00000.0"; 17 18 /++ 19 Stack-allocated decimal type. 20 Params: 21 size64 = count of 64bit words in coefficient 22 +/ 23 @serdeScoped @serdeProxy!(const(char)[]) 24 struct Decimal(uint size64) 25 if (size64 && size64 <= ushort.max) 26 { 27 import mir.format: NumericSpec; 28 import mir.bignum.integer; 29 import mir.bignum.low_level_view; 30 import std.traits: isMutable, isFloatingPoint; 31 32 /// 33 long exponent; 34 /// 35 BigInt!size64 coefficient; 36 37 /// 38 void toString(C = char, W)(ref scope W w, NumericSpec spec = NumericSpec.init) const scope 39 if(isSomeChar!C && isMutable!C) 40 { 41 assert(spec.format == NumericSpec.Format.exponent || spec.format == NumericSpec.Format.human); 42 import mir.utility: _expect; 43 // handle special values 44 if (_expect(exponent == exponent.max, false)) 45 { 46 static immutable C[3] nan = "nan"; 47 static immutable C[4] ninf = "-inf"; 48 static immutable C[4] pinf = "+inf"; 49 w.put(coefficient.length == 0 ? coefficient.sign ? ninf[] : pinf[] : nan[]); 50 return; 51 } 52 53 C[coefficientBufferLength + 16] buffer0 = void; 54 auto buffer = buffer0[0 .. $ - 16]; 55 56 size_t coefficientLength; 57 static if (size_t.sizeof == 8) 58 { 59 if (__ctfe) 60 { 61 uint[coefficient.data.length * 2] data; 62 foreach (i; 0 .. coefficient.length) 63 { 64 auto l = cast(uint)coefficient.data[i]; 65 auto h = cast(uint)(coefficient.data[i] >> 32); 66 data[i * 2 + 0] = l; 67 data[i * 2 + 1] = h; 68 } 69 auto work = BigUIntView!uint(data); 70 work = work.topLeastSignificantPart(coefficient.length * 2).normalized; 71 coefficientLength = work.toStringImpl(buffer); 72 } 73 else 74 { 75 BigInt!size64 work = void; 76 work = coefficient; 77 coefficientLength = work.view.unsigned.toStringImpl(buffer); 78 } 79 } 80 else 81 { 82 BigInt!size64 work = void; 83 work = coefficient; 84 coefficientLength = work.view.unsigned.toStringImpl(buffer); 85 } 86 87 C[1] sign = coefficient.sign ? "-" : "+"; 88 bool addSign = coefficient.sign || spec.plus; 89 long s = this.exponent + coefficientLength; 90 91 alias zeros = zerosImpl!C; 92 93 if (spec.format == NumericSpec.Format.human) 94 { 95 if (!spec.separatorCount) 96 spec.separatorCount = 3; 97 void putL(scope const(C)[] b) 98 { 99 assert(b.length); 100 101 if (addSign) 102 w.put(sign[]); 103 104 auto r = b.length % spec.separatorCount; 105 if (r == 0) 106 r = spec.separatorCount; 107 C[1] sep = spec.separatorChar; 108 goto LS; 109 do 110 { 111 w.put(sep[]); 112 LS: 113 w.put(b[0 .. r]); 114 b = b[r .. $]; 115 r = spec.separatorCount; 116 } 117 while(b.length); 118 } 119 120 // try print decimal form without exponent 121 // up to 6 digits exluding leading 0. or final .0 122 if (s <= 0) 123 { 124 //0.001.... 125 //0.0001 126 //0.00001 127 //0.000001 128 //If separatorChar is defined lets be less greed for space. 129 if (this.exponent >= -6 || s >= -2 - (spec.separatorChar != 0) * 3) 130 { 131 if (addSign) 132 w.put(sign[]); 133 w.put(zeros[0 .. cast(sizediff_t)(-s + 2)]); 134 w.put(buffer[$ - coefficientLength .. $]); 135 return; 136 } 137 } 138 else 139 if (this.exponent >= 0) 140 { 141 ///dddddd.0 142 if (!spec.separatorChar) 143 { 144 if (s <= 6) 145 { 146 buffer[$ - coefficientLength - 1] = sign[0]; 147 w.put(buffer[$ - coefficientLength - addSign .. $]); 148 w.put(zeros[($ - (cast(sizediff_t)this.exponent + 2)) .. $]); 149 return; 150 } 151 } 152 else 153 { 154 if (s <= 12) 155 { 156 buffer0[$ - 16 .. $] = '0'; 157 putL(buffer0[$ - coefficientLength - 16 .. $ - 16 + cast(sizediff_t)this.exponent]); 158 w.put(zeros[$ - 2 .. $]); 159 return; 160 } 161 } 162 } 163 else 164 { 165 ///dddddd.0 166 if (!spec.separatorChar) 167 { 168 ///dddddd.d.... 169 if (s <= 6 || coefficientLength <= 6) 170 { 171 buffer[$ - coefficientLength - 1] = sign[0]; 172 w.put(buffer[$ - coefficientLength - addSign .. $ - coefficientLength + cast(sizediff_t)s]); 173 T2: 174 buffer[$ - coefficientLength + cast(sizediff_t)s - 1] = '.'; 175 w.put(buffer[$ - coefficientLength + cast(sizediff_t)s - 1 .. $]); 176 return; 177 } 178 } 179 else 180 { 181 if (s <= 12 || coefficientLength <= 12) 182 { 183 putL(buffer[$ - coefficientLength .. $ - coefficientLength + cast(sizediff_t)s]); 184 goto T2; 185 } 186 } 187 } 188 } 189 190 assert(coefficientLength); 191 192 long exponent = s - 1; 193 194 if (coefficientLength > 1) 195 { 196 auto c = buffer[$ - coefficientLength]; 197 buffer[$ - coefficientLength] = '.'; 198 buffer[$ - ++coefficientLength] = c; 199 } 200 201 buffer[$ - coefficientLength - 1] = sign[0]; 202 w.put(buffer[$ - coefficientLength - addSign .. $]); 203 204 import mir.format_impl: printSignedToTail; 205 206 static if (exponent.sizeof == 8) 207 enum N = 21; 208 else 209 enum N = 11; 210 211 // prints e+/-exponent 212 auto expLength = printSignedToTail(exponent, buffer0[$ - N - 16 .. $ - 16], '+'); 213 buffer[$ - ++expLength] = spec.exponentChar; 214 w.put(buffer[$ - expLength .. $]); 215 } 216 217 @safe: 218 219 /// 220 DecimalView!size_t view() return scope 221 { 222 return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); 223 } 224 225 /// ditto 226 DecimalView!(const size_t) view() const return scope 227 { 228 return typeof(return)(coefficient.sign, exponent, coefficient.view.unsigned); 229 } 230 231 /// 232 this(C)(scope const(C)[] str, int exponentShift = 0) @safe pure @nogc 233 if (isSomeChar!C) 234 { 235 DecimalExponentKey key; 236 if (fromStringImpl(str, key, exponentShift) || key == DecimalExponentKey.nan || key == DecimalExponentKey.infinity) 237 return; 238 static if (__traits(compiles, () @nogc { throw new Exception("Can't parse Decimal."); })) 239 { 240 import mir.exception: MirException; 241 throw new MirException("Can't parse Decimal!" ~ size64.stringof ~ " from string `", str , "`"); 242 } 243 else 244 { 245 static immutable exception = new Exception("Can't parse Decimal!" ~ size64.stringof ~ "."); 246 { import mir.exception : toMutable; throw exception.toMutable; } 247 } 248 } 249 250 /++ 251 Constructs Decimal from the floating point number using the $(HTTPS github.com/ulfjack/ryu, Ryu algorithm). 252 253 The number is the shortest decimal representation that being converted back would result the same floating-point number. 254 +/ 255 this(T)(const T x) 256 if (isFloatingPoint!T && size64 >= 1 + (T.mant_dig >= 64)) 257 { 258 import mir.bignum.internal.ryu.generic_128: genericBinaryToDecimal; 259 this = genericBinaryToDecimal(x); 260 } 261 262 /// 263 ref opAssign(uint rhsMaxSize64)(auto ref scope const Decimal!rhsMaxSize64 rhs) return 264 if (rhsMaxSize64 < size64) 265 { 266 this.exponent = rhs.exponent; 267 this.coefficient = rhs.coefficient; 268 return this; 269 } 270 271 /++ 272 Handle thousand separators for non exponential numbers. 273 274 Returns: false in case of overflow or incorrect string. 275 +/ 276 bool fromStringWithThousandsSeparatorImpl(C, 277 bool allowSpecialValues = true, 278 bool allowStartingPlus = true, 279 bool allowLeadingZeros = true, 280 )( 281 scope const(C)[] str, 282 const C thousandsSeparator, 283 const C fractionSeparator, 284 out DecimalExponentKey key, 285 int exponentShift = 0, 286 ) @trusted 287 if (isSomeChar!C) 288 { 289 import mir.algorithm.iteration: find; 290 import mir.format: stringBuf; 291 import mir.ndslice.chunks: chunks; 292 import mir.ndslice.slice: sliced; 293 import mir.ndslice.topology: retro; 294 295 auto buffer = stringBuf; 296 assert(thousandsSeparator != fractionSeparator); 297 if (str.length && (str[0] == '+' || str[0] == '-')) 298 { 299 buffer.put(cast(char)str[0]); 300 str = str[1 .. $]; 301 } 302 auto integer = str[0 .. $ - str.find!(a => a == fractionSeparator)]; 303 if (integer.length % 4 == 0) 304 return false; 305 foreach_reverse (chunk; integer.sliced.retro.chunks(4)) 306 { 307 auto s = chunk.retro.field; 308 if (s.length == 4) 309 { 310 if (s[0] != thousandsSeparator) 311 return false; 312 s = s[1 .. $]; 313 } 314 do 315 { 316 if (s[0] < '0' || s[0] > '9') 317 return false; 318 buffer.put(cast(char)s[0]); 319 s = s[1 .. $]; 320 } 321 while(s.length); 322 } 323 if (str.length > integer.length) 324 { 325 buffer.put('.'); 326 str = str[integer.length + 1 .. $]; 327 if (str.length == 0) 328 return false; 329 do 330 { 331 buffer.put(cast(char)str[0]); 332 str = str[1 .. $]; 333 } 334 while(str.length); 335 } 336 return fromStringImpl!(char, 337 allowSpecialValues, 338 false, // allowDotOnBounds 339 false, // allowDExponent 340 allowStartingPlus, 341 false, // allowUnderscores 342 allowLeadingZeros, // allowLeadingZeros 343 false, // allowExponent 344 false, // checkEmpty 345 )(buffer.data, key, exponentShift); 346 } 347 348 /++ 349 Returns: false in case of overflow or incorrect string. 350 +/ 351 bool fromStringImpl(C, 352 bool allowSpecialValues = true, 353 bool allowDotOnBounds = true, 354 bool allowDExponent = true, 355 bool allowStartingPlus = true, 356 bool allowUnderscores = true, 357 bool allowLeadingZeros = true, 358 bool allowExponent = true, 359 bool checkEmpty = true, 360 ) 361 (scope const(C)[] str, out DecimalExponentKey key, int exponentShift = 0) 362 scope @trusted pure @nogc nothrow 363 if (isSomeChar!C) 364 { 365 import mir.bignum.low_level_view: DecimalView, BigUIntView, MaxWordPow10; 366 auto work = DecimalView!size_t(false, 0, BigUIntView!size_t(coefficient.data)); 367 auto ret = work.fromStringImpl!(C, 368 allowSpecialValues, 369 allowDotOnBounds, 370 allowDExponent, 371 allowStartingPlus, 372 allowUnderscores, 373 allowLeadingZeros, 374 allowExponent, 375 checkEmpty, 376 )(str, key, exponentShift); 377 coefficient.length = cast(uint) work.coefficient.coefficients.length; 378 coefficient.sign = work.sign; 379 exponent = work.exponent; 380 return ret; 381 } 382 383 private enum coefficientBufferLength = 2 + ceilLog10Exp2(coefficient.data.length * (size_t.sizeof * 8)); // including dot and sign 384 private enum eDecimalLength = coefficientBufferLength + expBufferLength; 385 386 /// 387 immutable(C)[] toString(C = char)(NumericSpec spec = NumericSpec.init) const scope @safe pure nothrow 388 if(isSomeChar!C && isMutable!C) 389 { 390 import mir.appender: UnsafeArrayBuffer; 391 C[eDecimalLength] data = void; 392 auto buffer = UnsafeArrayBuffer!C(data); 393 toString(buffer, spec); 394 return buffer.data.idup; 395 } 396 397 /++ 398 Mir parsing supports up-to quadruple precision. The conversion error is 0 ULP for normal numbers. 399 Subnormal numbers with an exponent greater than or equal to -512 have upper error bound equal to 1 ULP. +/ 400 T opCast(T, bool wordNormalized = true)() scope const 401 if (isFloatingPoint!T && isMutable!T) 402 { 403 return view.opCast!(T, wordNormalized); 404 } 405 406 /// 407 bool isNaN() scope const @property 408 { 409 return exponent == exponent.max && coefficient.length; 410 } 411 412 /// 413 bool isInfinity() scope const @property 414 { 415 return exponent == exponent.max && coefficient.length == 0; 416 } 417 418 /// 419 bool isSpecial() scope const @property 420 { 421 return exponent == exponent.max; 422 } 423 424 /// 425 ref opOpAssign(string op, size_t rhsMaxSize64)(ref const Decimal!rhsMaxSize64 rhs) @safe pure return 426 if (op == "+" || op == "-") 427 { 428 import mir.utility: max; 429 BigInt!(max(rhsMaxSize64, size64, 256u)) rhsCopy = void; 430 BigIntView!(const size_t) rhsView; 431 auto expDiff = cast(sizediff_t) (exponent - rhs.exponent); 432 if (expDiff >= 0) 433 { 434 exponent = rhs.exponent; 435 coefficient.mulPow5(expDiff); 436 coefficient.opOpAssign!"<<"(expDiff); 437 rhsView = rhs.coefficient.view; 438 } 439 else 440 { 441 rhsCopy.copyFrom(rhs.coefficient.coefficients, rhs.coefficient.sign); 442 rhsCopy.mulPow5(-expDiff); 443 rhsCopy.opOpAssign!"<<"(-expDiff); 444 rhsView = rhsCopy.view; 445 } 446 coefficient.opOpAssign!op(rhsView); 447 return this; 448 } 449 } 450 451 /// 452 version(mir_bignum_test) 453 @safe pure nothrow @nogc 454 unittest 455 { 456 import mir.test: should; 457 import mir.conv: to; 458 Decimal!128 decimal = void; 459 DecimalExponentKey key; 460 461 assert(decimal.fromStringImpl("3.141592653589793378e-10", key)); 462 decimal.opCast!double.should == 0x1.596bf8ce7631ep-32; 463 key.should == DecimalExponentKey.e; 464 } 465 466 /// 467 version(mir_bignum_test) 468 @safe pure nothrow @nogc 469 unittest 470 { 471 import mir.conv: to; 472 Decimal!3 decimal; 473 DecimalExponentKey key; 474 475 assert(decimal.fromStringImpl("0", key)); 476 assert(key == DecimalExponentKey.none); 477 assert(decimal.exponent == 0); 478 assert(decimal.coefficient.length == 0); 479 assert(!decimal.coefficient.sign); 480 assert(cast(double) decimal.coefficient == 0); 481 482 assert(decimal.fromStringImpl("-0.0", key)); 483 assert(key == DecimalExponentKey.dot); 484 assert(decimal.exponent == -1); 485 assert(decimal.coefficient.length == 0); 486 assert(decimal.coefficient.sign); 487 assert(cast(double) decimal.coefficient == 0); 488 489 assert(decimal.fromStringImpl("0e0", key)); 490 assert(key == DecimalExponentKey.e); 491 assert(decimal.exponent == 0); 492 assert(decimal.coefficient.length == 0); 493 assert(!decimal.coefficient.sign); 494 assert(cast(double) decimal.coefficient == 0); 495 } 496 497 /// 498 version(mir_bignum_test) @safe pure @nogc unittest 499 { 500 auto a = Decimal!1("777.7"); 501 auto b = Decimal!1("777"); 502 import mir.format; 503 assert(stringBuf() << cast(double)a - cast(double)b << getData == "0.7000000000000455"); 504 a -= b; 505 assert(stringBuf() << a << getData == "0.7"); 506 507 a = Decimal!1("-777.7"); 508 b = Decimal!1("777"); 509 a += b; 510 assert(stringBuf() << a << getData == "-0.7"); 511 512 a = Decimal!1("777.7"); 513 b = Decimal!1("-777"); 514 a += b; 515 assert(stringBuf() << a << getData == "0.7"); 516 517 a = Decimal!1("777"); 518 b = Decimal!1("777.7"); 519 a -= b; 520 assert(stringBuf() << a << getData == "-0.7"); 521 } 522 523 /// Check @nogc toString impl 524 version(mir_bignum_test) @safe pure @nogc unittest 525 { 526 import mir.format: stringBuf; 527 auto str = "5.28238923728e-876543210"; 528 auto decimal = Decimal!1(str); 529 auto buffer = stringBuf; 530 buffer << decimal; 531 assert(buffer.data == str); 532 } 533 534 /// 535 version(mir_bignum_test) 536 @safe pure @nogc unittest 537 { 538 Decimal!4 i = "-0"; 539 540 assert(i.view.coefficient.coefficients.length == 0); 541 assert(i.coefficient.view.unsigned.coefficients.length == 0); 542 assert(i.coefficient.view == 0L); 543 assert(cast(long) i.coefficient == 0); 544 assert(i.coefficient.sign); 545 } 546 547 /// 548 version(mir_bignum_test) @safe pure unittest 549 { 550 auto str = "-3.4010447314490204552169750449563978034784726557588085989975288830070948234680e-13245"; 551 auto decimal = Decimal!4(str); 552 assert(decimal.toString == str, decimal.toString); 553 554 decimal = Decimal!4.init; 555 assert(decimal.toString == "0.0"); 556 } 557 558 /// 559 version(mir_bignum_test) 560 @safe pure nothrow @nogc 561 unittest 562 { 563 import mir.test: should; 564 565 import mir.conv: to; 566 Decimal!3 decimal; 567 DecimalExponentKey key; 568 569 // Check precise percentate parsing 570 assert(decimal.fromStringImpl("71.7", key, -2)); 571 key.should == DecimalExponentKey.dot; 572 // The result is exact value instead of 0.7170000000000001 = 71.7 / 100 573 (cast(double) decimal).should == 0.717; 574 575 assert(decimal.fromStringImpl("+0.334e-5"w, key)); 576 key.should == DecimalExponentKey.e; 577 (cast(double) decimal).should == 0.334e-5; 578 579 assert(decimal.fromStringImpl("100_000_000"w, key)); 580 key.should == DecimalExponentKey.none; 581 (cast(double) decimal).should == 1e8; 582 583 assert(decimal.fromStringImpl("-334D-5"d, key)); 584 key.should == DecimalExponentKey.D; 585 (cast(double) decimal).should == -334e-5; 586 587 assert(decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); 588 key.should == DecimalExponentKey.none; 589 (cast(double) decimal).should == 2482734692817364218734682973648217364981273648923423.0; 590 591 assert(decimal.fromStringImpl(".023", key)); 592 key.should == DecimalExponentKey.dot; 593 (cast(double) decimal).should == .023; 594 595 assert(decimal.fromStringImpl("0E100", key)); 596 key.should == DecimalExponentKey.E; 597 (cast(double) decimal).should == 0; 598 599 foreach (str; ["-nan", "-NaN", "-NAN"]) 600 { 601 assert(decimal.fromStringImpl(str, key)); 602 assert(decimal.coefficient.length > 0); 603 assert(decimal.exponent == decimal.exponent.max); 604 assert(decimal.coefficient.sign); 605 key.should == DecimalExponentKey.nan; 606 auto nan = cast(double) decimal; 607 (cast(double) decimal).should == double.nan; 608 } 609 610 foreach (str; ["inf", "Inf", "INF"]) 611 { 612 assert(decimal.fromStringImpl(str, key)); 613 assert(decimal.coefficient.length == 0); 614 assert(decimal.exponent == decimal.exponent.max); 615 assert(key == DecimalExponentKey.infinity); 616 (cast(double) decimal).should == double.infinity; 617 } 618 619 assert(decimal.fromStringImpl("-inf", key)); 620 assert(decimal.coefficient.length == 0); 621 assert(decimal.exponent == decimal.exponent.max); 622 assert(key == DecimalExponentKey.infinity); 623 should(cast(double) decimal) == -double.infinity; 624 625 assert(!decimal.fromStringImpl("3.3.4", key)); 626 assert(!decimal.fromStringImpl("3.4.", key)); 627 assert(decimal.fromStringImpl("4.", key)); 628 assert(!decimal.fromStringImpl(".", key)); 629 assert(decimal.fromStringImpl("0.", key)); 630 assert(decimal.fromStringImpl("00", key)); 631 assert(!decimal.fromStringImpl("0d", key)); 632 } 633 634 version(mir_bignum_test) 635 @safe pure nothrow @nogc 636 unittest 637 { 638 import mir.conv: to; 639 Decimal!1 decimal; 640 DecimalExponentKey key; 641 642 assert(decimal.fromStringImpl("1.334", key)); 643 assert(key == DecimalExponentKey.dot); 644 assert(cast(double) decimal == 1.334); 645 646 assert(decimal.fromStringImpl("+0.334e-5"w, key)); 647 assert(key == DecimalExponentKey.e); 648 assert(cast(double) decimal == 0.334e-5); 649 650 assert(decimal.fromStringImpl("-334D-5"d, key)); 651 assert(key == DecimalExponentKey.D); 652 assert(cast(double) decimal == -334e-5); 653 654 assert(!decimal.fromStringImpl("2482734692817364218734682973648217364981273648923423", key)); 655 656 assert(decimal.fromStringImpl(".023", key)); 657 assert(key == DecimalExponentKey.dot); 658 assert(cast(double) decimal == .023); 659 660 assert(decimal.fromStringImpl("0E100", key)); 661 assert(key == DecimalExponentKey.E); 662 assert(cast(double) decimal == 0); 663 664 /++ Test that Issue #365 is handled properly +/ 665 assert(decimal.fromStringImpl("123456.e0", key)); 666 assert(key == DecimalExponentKey.e); 667 assert(cast(double) decimal == 123_456.0); 668 669 assert(decimal.fromStringImpl("123_456.e0", key)); 670 assert(key == DecimalExponentKey.e); 671 assert(cast(double) decimal == 123_456.0); 672 673 assert(decimal.fromStringImpl("123456.E0", key)); 674 assert(key == DecimalExponentKey.E); 675 assert(cast(double) decimal == 123_456.0); 676 677 assert(decimal.fromStringImpl("123_456.E0", key)); 678 assert(key == DecimalExponentKey.E); 679 assert(cast(double) decimal == 123_456.0); 680 681 assert(decimal.fromStringImpl("123456.d0", key)); 682 assert(key == DecimalExponentKey.d); 683 assert(cast(double) decimal == 123_456.0); 684 685 assert(decimal.fromStringImpl("123_456.d0", key)); 686 assert(key == DecimalExponentKey.d); 687 assert(cast(double) decimal == 123_456.0); 688 689 assert(decimal.fromStringImpl("123456.D0", key)); 690 assert(key == DecimalExponentKey.D); 691 assert(cast(double) decimal == 123_456.0); 692 693 assert(decimal.fromStringImpl("123_456.D0", key)); 694 assert(key == DecimalExponentKey.D); 695 assert(cast(double) decimal == 123_456.0); 696 697 /++ Test invalid examples with the fix introduced for Issue #365 +/ 698 assert(!decimal.fromStringImpl("123_456_.D0", key)); 699 assert(!decimal.fromStringImpl("123_456.DD0", key)); 700 assert(!decimal.fromStringImpl("123_456_.E0", key)); 701 assert(!decimal.fromStringImpl("123_456.EE0", key)); 702 assert(!decimal.fromStringImpl("123456.ED0", key)); 703 assert(!decimal.fromStringImpl("123456E0D0", key)); 704 assert(!decimal.fromStringImpl("123456._D0", key)); 705 assert(!decimal.fromStringImpl("123456_.D0", key)); 706 assert(!decimal.fromStringImpl("123456.E0D0", key)); 707 assert(!decimal.fromStringImpl("123456.D0_", key)); 708 assert(!decimal.fromStringImpl("123456_", key)); 709 710 foreach (str; ["-nan", "-NaN", "-NAN"]) 711 { 712 assert(decimal.fromStringImpl(str, key)); 713 assert(decimal.coefficient.length > 0); 714 assert(decimal.exponent == decimal.exponent.max); 715 assert(decimal.coefficient.sign); 716 assert(key == DecimalExponentKey.nan); 717 assert(cast(double) decimal != cast(double) decimal); 718 } 719 720 foreach (str; ["inf", "Inf", "INF"]) 721 { 722 assert(decimal.fromStringImpl(str, key)); 723 assert(decimal.coefficient.length == 0); 724 assert(decimal.exponent == decimal.exponent.max); 725 assert(key == DecimalExponentKey.infinity); 726 assert(cast(double) decimal == double.infinity); 727 } 728 729 assert(decimal.fromStringImpl("-inf", key)); 730 assert(decimal.coefficient.length == 0); 731 assert(decimal.exponent == decimal.exponent.max); 732 assert(key == DecimalExponentKey.infinity); 733 assert(cast(double) decimal == -double.infinity); 734 735 assert(!decimal.fromStringImpl("3.3.4", key)); 736 assert(!decimal.fromStringImpl("3.4.", key)); 737 assert(decimal.fromStringImpl("4.", key)); 738 assert(!decimal.fromStringImpl(".", key)); 739 assert(decimal.fromStringImpl("0.", key)); 740 assert(decimal.fromStringImpl("00", key)); 741 assert(!decimal.fromStringImpl("0d", key)); 742 } 743 744 /// 745 version(mir_bignum_test) 746 @safe pure @nogc unittest 747 { 748 import mir.math.constant: PI; 749 Decimal!2 decimal = "3.141592653589793378e-40"; // constructor 750 assert(cast(double) decimal == double(PI) / 1e40); 751 } 752 753 754 /// 755 version(mir_bignum_test) 756 @safe pure nothrow @nogc 757 unittest 758 { 759 // float and double can be used to construct Decimal of any length 760 auto decimal64 = Decimal!1(-1.235e-7); 761 assert(decimal64.exponent == -10); 762 assert(decimal64.coefficient == -1235); 763 764 // real number may need Decimal at least length of 2 765 auto decimal128 = Decimal!2(-1.235e-7L); 766 assert(decimal128.exponent == -10); 767 assert(decimal128.coefficient == -1235); 768 769 decimal128 = Decimal!2(1234e3f); 770 assert(decimal128.exponent == 3); 771 assert(decimal128.coefficient == 1234); 772 } 773 774 /// 775 version(mir_bignum_test) 776 @safe pure nothrow @nogc 777 unittest 778 { 779 Decimal!3 decimal; 780 DecimalExponentKey key; 781 782 assert(decimal.fromStringWithThousandsSeparatorImpl("12,345.678", ',', '.', key)); 783 assert(cast(double) decimal == 12345.678); 784 assert(key == DecimalExponentKey.dot); 785 786 assert(decimal.fromStringWithThousandsSeparatorImpl("12,345,678", ',', '.', key, -3)); 787 assert(cast(double) decimal == 12345.678); 788 assert(key == DecimalExponentKey.none); 789 790 assert(decimal.fromStringWithThousandsSeparatorImpl("021 345,678", ' ', ',', key)); 791 assert(cast(double) decimal == 21345.678); 792 assert(key == DecimalExponentKey.dot); 793 }