The OpenD Programming Language

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 }