1 /*This program is free software: you can redistribute it and/or modify
2 it under the terms of the GNU General Public License as published by
3 the Free Software Foundation, either version 3 of the License, or
4 (at your option) any later version.
5 
6 This program is distributed in the hope that it will be useful,
7 but WITHOUT ANY WARRANTY; without even the implied warranty of
8 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 GNU General Public License for more details.
10 
11 You should have received a copy of the GNU General Public License
12 along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
13 /** Copyright: 2022-2023, Ruby The Roobster*/
14 /**Author: Ruby The Roobster, <michaeleverestc79@gmail.com>*/
15 /**Date: January 16, 2023*/
16 /** License:  GPL-3.0*/
17 
18 /// Module for representing numbers.
19 module dutils.math.number;
20 
21 version(DLL)
22 {
23     export:
24 }
25 
26 version(Standard)
27 {
28     public:
29 }
30 
31 /// Core dutils math library.
32 public import dutils.math.core;
33 /// BigInt is a necessary type for our usage.
34 public import std.bigint;
35 
36 /// Class to represent Numbers in dutils.
37 class Number : Mtype!NumberContainer
38 {
39     /*****************************************************************
40      * Constructs a Number.
41      *
42      * Params:
43      *     num =
44      *         The value to contain within the Number.
45      */
46     this(in NumberContainer num = NumberContainer(BigInt(0), BigInt(0), 0)) pure @safe nothrow
47     {
48         super(num);
49     }
50 
51     /************************************************
52      * Constructs a Number using precision only.
53      *
54      * Params:
55      *     precision =
56      *          The precision of the NumberContainer.
57      */
58      this(ulong precision) pure @safe nothrow
59      {
60         super(precision);
61      }
62 
63     /***********************************************
64      * Represents a Number as a dstring.
65      *
66      * Returns:
67      *     The dstring representation of the Number.
68      */
69     override dstring toDstring() const @property pure @safe
70     {
71         import std.format;
72         char[] temp = "".dup;
73         pragma(inline, true) void inFunc(in size_t i, in BigInt val) pure @safe
74         {
75             char[] temp2 = format("%s", val).dup;
76             if(this.contained.pow10 > 0)
77             {
78                 temp ~= temp2;
79                 temp.length += this.contained.pow10;
80                 temp[$-this.contained.pow10 .. $] = '0';
81             }
82             else if(this.contained.pow10 < 0)
83             {
84                 if(val < BigInt(0))
85                 {
86                     temp2[0 .. $-1] = temp2[1 .. $].dup;
87                     --temp2.length;
88                 }
89                 temp ~= temp2;
90                 if(-this.contained.pow10 > temp2.length)
91                 {
92                     temp.length -= (this.contained.pow10);
93                     temp[i] = '.';
94                     temp[i+1 .. i+1-(this.contained.pow10+temp2.length)] = '0';
95                     temp[i+1-(this.contained.pow10 + temp2.length) .. $] = temp2.dup;
96                 }
97                 else if(temp2.length == -this.contained.pow10)
98                 {
99                     temp2 = temp[i-1-this.contained.pow10 .. $].dup;
100                     temp[i-1-this.contained.pow10] = '.';
101                     ++temp.length;
102                     temp[i-this.contained.pow10 .. $] = temp2.dup;
103                 }
104                 else
105                 {
106                     ++temp.length;
107                     temp[i-this.contained.pow10 .. $] = temp[i .. $-1].dup;
108                     temp[i-this.contained.pow10] = '.';
109                 }
110             }
111             else
112             {
113                 temp ~= temp2;
114             }
115         }
116         inFunc(0, this.contained.val);
117         if(this.contained.ival < 0)
118             temp ~= '-';
119         else
120             temp ~= '+';
121         inFunc(temp.length, this.contained.ival);
122         temp ~= 'i';
123         dchar[] temp2;
124         foreach(c; temp)
125         {
126             ++temp2.length;
127             temp2[$-1] = d(c);
128         }
129         return temp2.idup;
130     }
131 
132     /***********************************
133      * Serializes a dstring to a Number.
134      *
135      * Params:
136      *     from =
137      *         The dstring to serialize.
138      */
139     override void fromDstring(dstring from) pure @safe
140     {
141         dchar[] val;
142         dchar[] ival;
143         size_t i;
144         do
145         {
146             val ~= from[i];
147             ++i;
148         }
149         while(i == 0 || (from[i] != d('+') && from[i] != d('-')));
150         do
151         {
152             ival ~= from[i];
153             ++i;
154         }
155         while(i < from.length);
156 
157         i = 0;
158         long dec = -1;
159         long z = 0;
160         size_t firstz = 0;
161         size_t[] zi;
162         import std.conv : to;
163         bool neg = false;
164         if(val[0] == d('-'))
165         {
166             neg = true;
167             val[0 .. $-1] = val[1 .. $].dup;
168             --val.length;
169         }
170         pragma(inline, true) void inFunc(in dchar[] val, ref BigInt rval)
171         {
172             rval = BigInt(0);
173             do
174             {
175                 if(val[i] == d('.'))
176                 {
177                     dec = i;
178                     ++i;
179                     continue;
180                 }
181 
182                 else if(val[i] == d('0'))
183                 {
184                     if(firstz == 0)
185                         firstz = i;
186                     zi ~= i;
187                     ++i;
188                     continue;
189                 }
190                 BigInt temp = BigInt(10);
191                 temp ^^= (val.length-1-i);
192                 temp *= BigInt(to!ubyte([val[i]]));
193                 rval = temp;
194                 ++i;
195             }
196             while(i < val.length);
197             i = 0;
198 
199             if(neg)
200                 rval *= -1;
201             neg = false;
202         }
203         
204         inFunc(val, this.contained.val);
205         if(dec == -1)
206         {
207             this.contained.pow10 = zi.length;
208             do
209             {
210                 if(zi.length == 0)
211                     break;
212                 ++i;
213             }
214             while(i < zi.length-1 && zi[i-1] == zi[i] -1);
215             this.contained.val /= BigInt(10) ^^ i;
216         }
217         else if(firstz == 0)
218             this.contained.pow10 = -zi.length; 
219         else
220             this.contained.pow10 = -(val.length-1-dec);
221         firstz = 0;
222         zi = [];
223         z = 0;
224         i = 0;
225         dec = 0;
226         --ival.length; //The last character of ival is d('i'), which is a non integer.
227         if(ival[0] == d('-'))
228             neg = true;
229         ival[0 .. $-1] = ival[1 .. $].dup;
230         --ival.length;
231         inFunc(ival, this.contained.ival);
232     }
233 
234     /*************************************************
235      * Applies an operator to a Number given the rhs.
236      *
237      * Params:
238      *     op =
239      *         The operator to apply.
240      *     rhs =
241      *         The right hand side of the expression.
242      * Returns:
243      *     Whether the operation was succesful or not.
244      */
245     bool applyOp(W)(in dstring op, in Mtype!W rhs) pure @safe
246     in
247     {
248         import std.conv : to;
249         assert(is(W == NumberContainer));
250         assert((op == "+"d) ^ (op == "-"d) ^ (op == "*"d) ^ (op == "/"d) ^ (op == "^^"d), to!string(op));
251     }
252     do
253     {
254         Switch: final switch(op)
255         {
256             static foreach(o; ["+"d, "-"d, "*"d, "/"d, "^^"d])
257             {
258                 case o:
259                     mixin("this.contained "d ~ o ~ "= rhs.contained;"d);
260                     break Switch;
261             }
262         }
263         return true; //We assume that the contract hasn't been violated.
264     }
265 
266     /*************************************************
267      * The result of applying an operator to a Number.
268      *
269      * Params:
270      *     op =
271      *         The operator to apply.
272      *     rhs =
273      *         THe right hand side of the expression.
274      * Returns:
275      *     The result of the expression.
276      */
277     Number applyOpResult(W)(dstring op, Mtype!W rhs) pure @safe
278     {
279         Number temp = new Number();
280         temp.val = this.val;
281         temp.applyOp!W(op, rhs);
282         return new Number(temp.val);
283     }
284 
285     /*****************************************
286      * Detrmine if two Numbers are equal.
287      *
288      * Params:
289      *     rhs =
290      *        The Number to compare `this` to.
291      * Returns:
292      *     Whether `this` and rhs are equal.
293      */
294     bool opEquals(in Number rhs) pure const @safe nothrow @nogc
295     {
296         return (this.contained == rhs.contained);
297     }
298 }
299 
300 ///
301 pure @safe unittest {
302     BigInt a = 1;
303     immutable BigInt b = -1;
304     immutable long c = 0;
305     Number e = new Number(NumberContainer(a,b,c));
306     a = 2;
307     Number f = new Number(NumberContainer(a,b,c));
308     e.applyOp("/", f);
309     assert(e.val == NumberContainer(BigInt(6), BigInt(-2), -1L));
310     assert(e.toDstring == ".6-.2i"d);
311     f = new Number(NumberContainer(BigInt(6), BigInt(0), 1L));
312     assert(f.toDstring == "60+00i"d);
313     auto g = f.toDstring;
314     f.fromDstring(g);
315     assert(f.toDstring == g, cast(char[])f.toDstring.dup);
316     f = new Number(NumberContainer(BigInt(6), BigInt(0), -2L));
317     assert(f.toDstring == ".06+.00i"d, cast(char[])f.toDstring.dup);
318     g = f.toDstring;
319     f.fromDstring(g);
320     assert(g == f.toDstring);
321     f = new Number(NumberContainer(BigInt(3), BigInt(6), -23L));
322     g = f.toDstring;
323     f.fromDstring(g);
324     assert(g == f.toDstring);
325     f = new Number(NumberContainer(BigInt(6), BigInt(0), 0L));
326     assert(f.toDstring == "6+0i"d);
327     f = new Number(NumberContainer(BigInt(60), BigInt(0), -1L));
328     assert(f.toDstring == "6.0+.0i"d, cast(char[])f.toDstring.dup);
329 }
330 
331 /// Type that is contained by Number.
332 struct NumberContainer
333 {
334     /**********************************************************************************
335      * Constructs a NumberContainer.
336      *
337      * Params:
338      *     val =
339      *         The real part of the NumberContainer, expressed as a BigInt.
340      *     ival =
341      *         The imaginary part of the NumberContainer, expressed as a BigInt.
342      *     pow10 =
343      *         The power of 10 to multiply both parts by.
344      *     precision =
345      *         The digits of precision to use in divison and exponentiation operations.
346      */
347     this(BigInt val, BigInt ival = 0, long pow10 = 0, ulong precision = 18) pure @safe nothrow @nogc
348     {
349         this.val = val;
350         this.pow10 = pow10;
351         this.ival = ival;
352         this.precision = precision;
353     }
354 
355     /*******************************************************
356      * Assigns a NumberContainer to another NumberContainer.
357      *
358      * Params:
359      *     rhs =
360      *         The NumberContainer to assign.
361      */
362     void opAssign(in NumberContainer rhs) pure @safe nothrow @nogc
363     {
364         this.pow10 = rhs.pow10;
365         this.val = rhs.val;
366         this.ival = rhs.ival;
367         this.precision = rhs.precision;
368     }
369 
370     /*************************************************
371      * Overloading of the binary assignment operators.
372      *
373      * Params:
374      *     rhs =
375      *         The right hand side of the expression.
376      */
377     void opOpAssign(string op)(NumberContainer rhs) pure @safe
378     {
379         import std.algorithm : max;
380         static if(op == "/")
381         {
382             //Because BigInt is strictly an integer type, it is easier to code A/B as A*1/B, because the * operator is a piece of cake, and 1 over a BigInt is easier than an arbitrary BigInt
383             //over another arbitrary BigInt
384             immutable BigInt den = rhs.val ^^ 2 + rhs.ival ^^ 2;
385             NumberContainer store = NumberContainer(cast(BigInt)0);
386             auto istore = NumberContainer(cast(BigInt)0, cast(BigInt)0);
387             long count = 0;
388             ubyte count2 = 9;
389             immutable bool sign = (rhs.ival < 0); //Fix the infinite loop that occurs for negative values of rhs.ival.
390             if(sign)
391                 rhs.ival *= -1;
392             for(ulong i = 0; i < this.precision; ++i) //Real part
393             {
394                 if(rhs.val == BigInt(0))
395                     break;
396                 //Play around with the multiplier so the denominator actually fits in the numerator.
397                 while(den > (BigInt(10) ^^ count) * rhs.val)
398                 {
399                     ++count;
400                 }
401                 //Remove excess.
402                 while(den < (BigInt(10) ^^ (count - 1L) * rhs.val))
403                 {
404                     --count;
405                 }
406 
407                 for(; count2 * den > (BigInt(10) ^^ count) * rhs.val; --count2)
408                 {
409                         if(count2 < -9) //Remember, negative numbers exist too!
410                             throw new Exception("ERROR: Division by 0");
411                 }
412 
413                 rhs.val *= (BigInt(10) ^^ count); //`rhs` is a copy, so this isn't an issue.
414                 rhs.val -= count2 * den; //Continue performing long division.
415                 store.val *= 10;
416                 store.val += count2;
417                 store.pow10 -= count;
418 
419                 count = 0;
420                 count2 = 9;
421             }
422 
423 
424             for(ulong i = 0; i < precision; ++i) //Imaginary part.
425             {
426                 if(rhs.ival == BigInt(0))
427                     break;
428                 while(den > (BigInt(10) ^^ count) * rhs.ival)
429                 {
430                     ++count;
431                 }
432                 //Remove excess.
433                 while(den < (BigInt(10) ^^ (count - 1L) * rhs.ival))
434                 {
435                     --count;
436                 }
437 
438                 for(; count2 * den > (BigInt(10) ^^ count) * rhs.ival; --count2)
439                 {
440                         if(count2 < -9) //Remember, negative numbers exist too!
441                             throw new Exception("ERROR: Division by 0");
442                 }
443 
444                 rhs.ival *= (BigInt(10) ^^ count); //`rhs` is a copy, so this isn't an issue.
445                 rhs.ival -= count2 * den; //Continue performing long division.
446                 istore.ival *= 10;
447                 istore.ival += count2;
448                 istore.pow10 -= count;
449 
450                 count = 0;
451                 count2 = 9;
452             }
453             import std.algorithm : min, max;
454             if(!sign)
455                  istore.ival *= -1;
456             store += istore;
457             this *= store;
458         }
459         else static if(op == "^^")
460         {
461             //Oy Vey:  I ain't implementing this until Riemann Sums and oprators exist.
462         }
463         else static if(op == "*")
464         {
465             immutable temp = this.val;
466             this.val *= rhs.val;
467             this.val -= (this.ival * rhs.ival);
468             this.ival = (this.ival * rhs.val);
469             this.pow10 += rhs.pow10;
470             this.ival += (temp * rhs.ival);
471         }
472         else
473         {
474             if(this.pow10 > rhs.pow10)
475             {
476                 this.val *= BigInt(10) ^^ (this.pow10 - rhs.pow10);
477                 this.ival *= BigInt(10) ^^ (this.pow10 - rhs.pow10);
478             }
479             else if(rhs.pow10 > this.pow10)
480             {
481                 rhs.val *= BigInt(10) ^^ (rhs.pow10 - this.pow10);
482                 rhs.ival *= BigInt(10) ^^ (rhs.pow10 - this.pow10);
483             }
484             this.pow10 = rhs.pow10;
485             mixin("this.val " ~ op ~ "= rhs.val;");
486             mixin("this.ival " ~ op ~ "= rhs.ival;");
487         }
488     }
489 
490     /*******************************************************
491      * Overloading of the binary operators.
492      *
493      * Params:
494      *     rhs =
495      *         The right hand side of the expression.
496      * Returns:
497      *     The result of the expression.
498      */
499     NumberContainer opBinary(string op)(NumberContainer rhs) pure @safe
500     {
501         NumberContainer ret = this;
502         mixin("ret " ~ op ~ " rhs;");
503         return ret;
504     }
505 
506     /*************************************
507      * Determine if two Numbers are equal.
508      *
509      * Params:
510      *     rhs =
511      *         The Number to compare with.
512      * Returns:
513      *     Whether this is equal to rhs.
514      */
515     bool opEquals(in NumberContainer rhs) pure @safe nothrow const @nogc
516     {
517         return ((this.val == rhs.val) && (this.ival == rhs.ival))
518         && ((this.pow10 == rhs.pow10) && (this.precision == rhs.precision));
519     }
520     package:
521         BigInt val;
522         BigInt ival;
523         static if(is(size_t == ulong))
524             long pow10;
525         else static if(is(size_t == uint))
526             int pow10;
527         ulong precision;
528 }