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 }