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 }