root/trunk/rational/rational.d

Revision 795, 28.9 kB (checked in by dsimcha, 5 days ago)

Add casting to integer.

Line 
1 /**A rational number implementation
2  * By:  David Simcha
3  *
4  * License:
5  * Boost Software License - Version 1.0 - August 17th, 2003
6  *
7  * Permission is hereby granted, free of charge, to any person or organization
8  * obtaining a copy of the software and accompanying documentation covered by
9  * this license (the "Software") to use, reproduce, display, distribute,
10  * execute, and transmit the Software, and to prepare derivative works of the
11  * Software, and to permit third-parties to whom the Software is furnished to
12  * do so, all subject to the following:
13  *
14  * The copyright notices in the Software and this entire statement, including
15  * the above license grant, this restriction and the following disclaimer,
16  * must be included in all copies of the Software, in whole or in part, and
17  * all derivative works of the Software, unless such copies or derivative
18  * works are solely in the form of machine-executable object code generated by
19  * a source language processor.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24  * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25  * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  */
29 module rational;
30
31
32 import std.algorithm, std.stdio, std.bigint, std.conv, std.math, std.exception,
33        std.conv, std.traits;
34
35 alias std.math.abs abs;  // Allow cross-module overloading.
36
37 private template isIntegerLike(T) {
38     enum bool isIntegerLike = is(typeof({
39         T num;
40         num = 2;
41         num <<= 1;
42         num >>= 1;
43         num += num;
44         num *= num;
45         num /= num;
46         num -= num;
47         num %= 2;
48         num %= num;
49         bool foo = num < 2;
50         bool bar = num == 2;
51
52         return num;
53     }));
54 }
55
56 unittest {
57     static assert(isIntegerLike!BigInt);
58     static assert(isIntegerLike!int);
59     static assert(isIntegerLike!byte);
60     static assert(!isIntegerLike!real);
61 }
62
63 private template isRational(T) {
64     enum bool isRational =
65         is(typeof(T.init.denom)) && is(typeof(T.init.num));
66 }
67
68 private template CommonRational(R1, R2) {
69     static if(isRational!R1) {
70         alias CommonRational!(typeof(R1.num), R2) CommonRational;
71     } else static if(isRational!R2) {
72         alias CommonRational!(R1, typeof(R2.num)) CommonRational;
73     } else static if(is(CommonInteger!(R1, R2))) {
74         alias Rational!(CommonInteger!(R1, R2)) CommonRational;
75     }
76 }
77
78 /**
79 Returns $(D true) iff a value of type $(D U) can be assigned to a variable of
80 type $(D T).
81
82 Examples:
83 ---
84 static assert(isAssignable!(long, int));
85 static assert(!isAssignable!(int, long));
86 static assert(isAssignable!(const(char)[], string));
87 static assert(!isAssignable!(string, char[]));
88 ---
89 */
90 template isAssignable(T, U) {
91     enum bool isAssignable = is(typeof({
92         T t;
93         U u;
94         t = u;
95         return t;
96     }));
97 }
98
99 unittest {
100     static assert(isAssignable!(long, int));
101     static assert(!isAssignable!(int, long));
102     static assert(isAssignable!(const(char)[], string));
103     static assert(!isAssignable!(string, char[]));
104 }
105
106 /**Returns a common integral type between I1, I2.  This is defined as the type
107  * returned by I1.init * I2.init.
108  */
109 template CommonInteger(I1, I2) if(isIntegerLike!I1 && isIntegerLike!I2) {
110     alias typeof(I1.init * I2.init) CommonInteger;
111 }
112
113 unittest {
114     static assert(is(CommonInteger!(BigInt, int) == BigInt));
115     static assert(is(CommonInteger!(byte, int) == int));
116 }
117
118 /**Implements rational numbers on top of whatever integer type is specified
119  * by the user.  If you use a fixed-size int, the onus is on you to make
120  * sure nothing overflows.  Neither the denominator nor the numerator may
121  * be bigger than the maximum value of the underlying integer.
122  *
123  * If you use an arbitrary precision type, it must have the relevant operators
124  * overloaded and have value semantics.
125  *
126  * Examples:
127  * ---
128  * auto r1 = rational( BigInt("314159265"), BigInt("27182818"));
129  * auto r2 = rational( BigInt("8675309"), BigInt("362436"));
130  * r1 += r2;
131  * assert(r1 == rational( BigInt("174840986505151"),
132  *     BigInt("4926015912324")));
133  *
134  * // Print result.  Prints:
135  * // "174840986505151 / 4926015912324"
136  * writeln(f1);
137  *
138  * // Print result in decimal form.  Prints:
139  * // "35.4934"
140  * writeln(cast(real) result);
141  * ---
142  */
143 Rational!(CommonInteger!(I1, I2)) rational(I1, I2)(I1 i1, I2 i2)
144 if(isIntegerLike!I1 && isIntegerLike!I2) {
145     static if(is(typeof(typeof(return)(i1, i2)))) {
146         // Avoid initializing and then reassigning.
147         auto ret = typeof(return)(i1, i2);
148     } else {
149         // Don't want to use void initialization b/c BigInts probably use
150         // assignment operator, copy c'tor, etc.
151         typeof(return) ret;
152         ret.numerator = i1;
153         ret.denominator = i2;
154     }
155     ret.simplify();
156     return ret;
157 }
158
159 /**Overload for creating rational that initially has an integer value.*/
160 Rational!(I) rational(I)(I val)
161 if(isIntegerLike!I) {
162      return rational(val, 1);
163 }
164
165 ///
166 struct Rational(Int) if(isIntegerLike!Int) {
167 public:
168
169 // ----------------Multiplication operators----------------------------------
170     auto opBinary(string op, Rhs)(Rhs rhs)
171     if(op == "*" && is(CommonRational!(Int, Rhs)) && isRational!Rhs) {
172         auto ret = CommonRational!(Int, Rhs)(this.num, this.denom);
173         return ret *= rhs;
174     }
175
176     auto opBinary(string op, Rhs)(Rhs rhs)
177     if(op == "*" && is(CommonRational!(Int, Rhs)) && isIntegerLike!Rhs) {
178         auto ret = this;
179         return ret *= rhs;
180     }
181
182     auto opBinaryRight(string op, Rhs)(Rhs rhs)
183     if(op == "*" && is(CommonRational!(Int, Rhs)) && isIntegerLike!Rhs) {
184         return opBinary!(op, Rhs)(rhs);
185     }
186
187     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
188     if(op == "*" && isRational!Rhs) {
189         // Cancel common factors first, then multiply.  This prevents
190         // overflows and is much more efficient when using BigInts.
191         auto divisor = gcf(this.numerator, rhs.denominator);
192         this.numerator /= divisor;
193         rhs.denominator /= divisor;
194
195         divisor = gcf(this.denominator, rhs.numerator);
196         this.denominator /= divisor;
197         rhs.numerator /= divisor;
198
199         this.numerator *= rhs.numerator;
200         this.denominator *= rhs.denominator;
201
202         // Don't need to simplify.  Already cancelled common factors before
203         // multiplying.
204         fixSigns();
205         return this;
206     }
207
208     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
209     if(op == "*" && isIntegerLike!Rhs) {
210         auto divisor = gcf(this.denominator, rhs);
211         this.denominator /= divisor;
212         rhs /= divisor;
213         this.numerator *= rhs;
214
215         // Don't need to simplify.  Already cancelled common factors before
216         // multiplying.
217         fixSigns();
218         return this;
219     }
220
221 // --------------------Division operators--------------------------------------
222
223     auto opBinary(string op, Rhs)(Rhs rhs)
224     if(op == "/" && is(CommonRational!(Int, Rhs)) && isRational!Rhs) {
225         // Division = multiply by inverse.
226         swap(rhs.numerator, rhs.denominator);
227         return this *= rhs;
228     }
229
230     typeof(this) opBinary(string op, Rhs)(Rhs rhs)
231     if(op == "/" && is(CommonRational!(Int, Rhs)) && isIntegerLike!(Rhs)) {
232         auto ret = CommonRational!(Int, Rhs)(this.num, this.denom);
233         return ret /= rhs;
234     }
235
236     typeof(this) opBinaryRight(string op, Rhs)(Rhs rhs)
237     if(op == "/" && is(CommonRational!(Int, Rhs)) && isIntegerLike!Rhs) {
238         auto ret = CommonRational!(Int, Rhs)(this.denom, this.num);
239         return ret *= rhs;
240     }
241
242     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
243     if(op == "/" && isIntegerLike!Rhs) {
244         auto divisor = gcf(this.numerator, rhs);
245         this.numerator /= divisor;
246         rhs /= divisor;
247         this.denominator *= rhs;
248
249         // Don't need to simplify.  Already cancelled common factors before
250         // multiplying.
251         fixSigns();
252         return this;
253     }
254
255     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
256     if(op == "/" && isRational!Rhs) {
257         // Division = multiply by inverse.
258         swap(rhs.numerator, rhs.denominator);
259         return this *= rhs;
260     }
261
262 // ---------------------Addition operators-------------------------------------
263     auto opBinary(string op, Rhs)(Rhs rhs)
264     if(op == "+" && (isRational!Rhs || isIntegerLike!Rhs)) {
265         auto ret = CommonRational!(typeof(this), Rhs)(this.num, this.denom);
266         return ret += rhs;
267     }
268
269     auto opBinaryRight(string op, Rhs)(Rhs rhs)
270     if(op == "+" && is(CommonRational!(Int, Rhs)) && isIntegerLike!Rhs) {
271         return opBinary!(op, Rhs)(rhs);
272     }
273
274     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
275     if(op == "+" && isRational!Rhs) {
276         if(this.denominator == rhs.denominator) {
277             this.numerator += rhs.numerator;
278             simplify();
279             return this;
280         }
281
282         Int commonDenom = lcm(this.denominator, rhs.denominator);
283         this.numerator *= commonDenom / this.denominator;
284         this.numerator += (commonDenom / rhs.denominator) * rhs.numerator;
285         this.denominator = commonDenom;
286
287         simplify();
288         return this;
289     }
290
291     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
292     if(op == "+" && isIntegerLike!Rhs) {
293         this.numerator += rhs * this.denominator;
294
295         simplify();
296         return this;
297     }
298
299 // -----------------------Subtraction operators-------------------------------
300
301     auto opBinary(string op, Rhs)(Rhs rhs)
302     if(op == "-" && is(CommonRational!(Int, Rhs))) {
303         auto ret = CommonRational!(typeof(this), Rhs)(this.num, this.denom);
304         return ret -= rhs;
305     }
306
307     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
308     if(op == "-" && isRational!Rhs) {
309         if(this.denominator == rhs.denominator) {
310             this.numerator -= rhs.numerator;
311             simplify();
312             return this;
313         }
314
315         auto commonDenom = lcm(this.denominator, rhs.denominator);
316         this.numerator *= commonDenom / this.denominator;
317         this.numerator -= (commonDenom / rhs.denominator) * rhs.numerator;
318         this.denominator = commonDenom;
319
320         simplify();
321         return this;
322     }
323
324     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
325     if(op == "-" && isIntegerLike!Rhs) {
326         this.numerator -= rhs * this.denominator;
327
328         simplify();
329         return this;
330     }
331
332     typeof(this) opBinaryRight(string op, Rhs)(Rhs rhs)
333     if(op == "-" && is(CommonInteger!(Int, Rhs)) && isIntegerLike!Rhs) {
334         typeof(this) ret;
335         ret.denominator = this.denominator;
336         ret.numerator = (rhs * this.denominator) - this.numerator;
337
338         simplify();
339         return ret;
340     }
341
342 // ----------------------Unary operators---------------------------------------
343
344     typeof(this) opUnary(string op)() if(op == "-" || op == "+") {
345         mixin("return typeof(this)(" ~ op ~ "numerator, denominator);");
346     }
347
348 // ---------------------Exponentiation operator---------------------------------
349
350     // Can only handle integer powers if the result has to also be
351     // rational.
352
353     typeof(this) opOpAssign(string op, Rhs)(Rhs rhs)
354     if(op == "^^" && isIntegerLike!Rhs) {
355         if(rhs < 0) {
356             this.invert();
357             rhs *= -1;
358         }
359
360         /* Don't need to simplify here.  this is already simplified, meaning
361            the numerator and denominator don't have any common factors.  Raising
362            both to a positive integer power won't create any.
363          */
364          numerator ^^= rhs;
365          denominator ^^= rhs;
366          return this;
367     }
368
369     auto opBinary(string op, Rhs)(Rhs rhs)
370     if(op == "^^" && isIntegerLike!Rhs && is(CommonRational!(Int, Rhs))) {
371         auto ret = CommonRational!(Int, Rhs)(this.num, this.denom);
372         ret ^^= rhs;
373         return ret;
374     }
375
376 // ---------------------Assignment operators------------------------------------
377     typeof(this) opAssign(Rhs)(Rhs rhs)
378     if(isIntegerLike!Rhs && isAssignable!(Int, Rhs)) {
379         this.numerator = rhs;
380         this.denominator = 1;
381         return this;
382     }
383
384     typeof(this) opAssign(Rhs)(Rhs rhs)
385     if(isRational!Rhs && isAssignable!(Int, typeof(Rhs.num))) {
386         this.numerator = rhs.num;
387         this.denominator = rhs.denom;
388         return this;
389     }
390
391 // --------------------Comparison/Equality Operators---------------------------
392
393     bool opEquals(Rhs)(Rhs rhs) if(isRational!Rhs || isIntegerLike!Rhs) {
394         static if(isRational!Rhs) {
395             return rhs.numerator == this.numerator &&
396                 rhs.denominator == this.denominator;
397         } else {
398             static assert(isIntegerLike!Rhs);
399             return rhs == this.numerator && this.denominator == 1;
400         }
401     }
402
403     int opCmp(Rhs)(Rhs rhs) if(isRational!Rhs) {
404         if( opEquals(rhs)) {
405             return 0;
406         }
407
408         // Check a few obvious cases first, see if we can avoid having to use a
409         // common denominator.  These are basically speed hacks.
410
411         // Assumption:  When simplify() is called, rational will be written in
412         // canonical form, with any negative signs being only in the numerator.
413         if(this.numerator < 0 && rhs.numerator > 0) {
414             return -1;
415         } else if(this.numerator > 0 && rhs.numerator < 0) {
416             return 1;
417         } else if(this.numerator >= rhs.numerator &&
418             this.denominator <= rhs.denominator) {
419             // We've already ruled out equality, so this must be > rhs.
420             return 1;
421         } else if(rhs.numerator >= this.numerator &&
422             rhs.denominator <= this.denominator) {
423             return -1;
424         }
425
426         // Can't do it without common denominator.  Argh.
427         auto commonDenom = lcm(this.denominator, rhs.denominator);
428         auto lhsNum = this.numerator * (commonDenom / this.denominator);
429         auto rhsNum = rhs.numerator * (commonDenom / rhs.denominator);
430
431         if(lhsNum > rhsNum) {
432             return 1;
433         } else if(lhsNum < rhsNum) {
434             return -1;
435         }
436
437         // We've checked for equality already.  If we get to this point,
438         // there's clearly something wrong.
439         assert(0);
440     }
441
442     int opCmp(Rhs)(Rhs rhs) if(isIntegerLike!Rhs) {
443         if( opEquals(rhs)) {
444             return 0;
445         }
446
447         // Again, check the obvious cases first.
448         if(rhs >= this.numerator) {
449             return -1;
450         }
451
452         rhs *= this.denominator;
453         if(rhs > this.numerator) {
454             return -1;
455         } else if(rhs < this.numerator) {
456             return 1;
457         }
458
459         // Already checked for equality.  If we get here, something's wrong.
460         assert(0);
461     }
462
463 ///////////////////////////////////////////////////////////////////////////////
464
465     /**Fast inversion, equivalent to 1 / rational.*/
466     typeof(this) invert() {
467         swap(numerator, denominator);
468         return this;
469     }
470
471     /**Convert to floating point representation.*/
472     F opCast(F)() if(isFloatingPoint!F) {
473         // Do everything in real precision, then convert to F at the end.
474
475         static if(isIntegral!(Int)) {
476             return cast(real) numerator / denominator;
477         } else {
478             auto temp = this;
479             real expon = 1.0;
480             real ans = 0;
481             byte sign = 1;
482             if(temp.numerator < 0) {
483                 temp.numerator *= -1;
484                 sign = -1;
485             }
486
487             while(temp.numerator > 0) {
488                 while(temp.numerator < temp.denominator) {
489
490                     assert(temp.denominator > 0);
491
492                     static if(is(typeof(temp.denominator & 1))) {
493                         // Try to make numbers smaller instead of bigger.
494                         if((temp.denominator & 1) == 0) {
495                             temp.denominator >>= 1;
496                         } else {
497                             temp.numerator <<= 1;
498                         }
499                     } else {
500                         temp.numerator <<= 1;
501                     }
502
503                     expon *= 0.5;
504
505                     // This checks for overflow in case we're working with a
506                     // user-defined fixed-precision integer.
507                     enforce(temp.numerator > 0, text(
508                         "Overflow while converting ", typeof(this).stringof,
509                         " to ", F.stringof, "."));
510
511                 }
512
513                 auto intPart = temp.numerator / temp.denominator;
514
515                 static if(is(Int == std.bigint.BigInt)) {
516                     // This should really be a cast, but BigInt still has a few
517                     // issues.
518                     long lIntPart = intPart.toLong();
519                 } else {
520                     long lIntPart = cast(long) intPart;
521                 }
522
523                 // Test for changes.
524                 real oldAns = ans;
525                 ans += lIntPart * expon;
526                 if(ans == oldAns) {  // Smaller than epsilon.
527                     return ans * sign;
528                 }
529
530                 // Subtract out int part.
531                 temp.numerator -= intPart * temp.denominator;
532             }
533
534             return ans * sign;
535         }
536     }
537
538     /**Casts this to an integer by truncating the fractional part.  Equivalent
539      * to $(D integerPart), and then casting it to type $(D I).
540      */
541     I opCast(I)() if(isIntegerLike!I && is(typeof(cast(I) Int.init))) {
542         return cast(I) integerPart;
543     }
544
545     /**Returns the numerator.*/
546     @property Int num() {
547         return numerator;
548     }
549
550     /**Returns the denominator.*/
551     @property Int denom() {
552         return denominator;
553     }
554
555     /**Returns the integer part of this rational, with any remainder truncated.
556      */
557     @property Int integerPart() {
558         return num / denom;
559     }
560
561     /**Returns the fractional part of this rational.*/
562     @property typeof(this) fractionPart() {
563         return this - integerPart;
564     }
565
566     string toString() {
567         static if(is(Int == std.bigint.BigInt)) {
568             // Special case it for now.  This should be fixed later.
569             return toDecimalString(numerator) ~ " / " ~
570                 toDecimalString(denominator);
571         } else {
572             return to!string(numerator) ~ " / " ~ to!string(denominator);
573         }
574     }
575
576 private :
577     Int numerator;
578     Int denominator;
579
580     void simplify() {
581         if(numerator == 0) {
582             denominator = 1;
583             return;
584         }
585
586         auto divisor = gcf(numerator, denominator);
587         numerator /= divisor;
588         denominator /= divisor;
589
590         fixSigns();
591     }
592
593     void fixSigns() {
594         static if( !is(Int == ulong) && !is(Int == uint) &&
595             !is(Int == ushort) && !is(Int == ubyte)) {
596             // Write in canonical form w.r.t. signs.
597             if(denominator < 0) {
598                 denominator *= -1;
599                 numerator *= -1;
600             }
601         }
602     }
603 }
604
605 unittest {
606     // All reference values from the Maxima computer algebra system.
607
608     // Test c'tor and simplification first.
609     auto num = BigInt("295147905179352825852");
610     auto den = BigInt("147573952589676412920");
611     auto simpNum = BigInt("24595658764946068821");
612     auto simpDen = BigInt("12297829382473034410");
613     auto f1 = rational(num, den);
614     auto f2 = rational(simpNum, simpDen);
615     assert(f1 == f2);
616
617     // Test multiplication.
618     assert( rational(8, 42) * rational(cast(byte) 7, cast(byte) 68)
619         == rational(1, 51));
620     assert(rational(20_000L, 3_486_784_401U) * rational(3_486_784_401U, 1_000U)
621         == rational(20, 1));
622     auto f3 = rational(7, 57);
623     f3 *= rational(2, 78);
624     assert(f3 == rational(7, 2223));
625     f3 = 5 * f3;
626     assert(f3 == rational(35, 2223));
627     assert(f3 * 5UL == 5 * f3);
628
629     // Test division.  Since it's implemented in terms of multiplication,
630     // quick and dirty tests should be good enough.
631     assert( rational(7, 38) / rational(8, 79) == rational(553, 304));
632     assert( rational(7, 38) / rational(8, 79) == rational(553, 304));
633     auto f4 = rational(7, 38);
634     f4 /= rational(8UL, 79);
635     assert(f4 == rational(553, 304));
636     f4 = f4 / 2;
637     assert(f4 == rational(553, 608));
638     f4 = 2 / f4;
639     assert(f4 == rational(1216, 553));
640     assert(f4 * 2 == f4 * rational(2));
641     f4 = 2;
642     assert(f4 == 2);
643
644     // Test addition.
645     assert( rational(1, 3) + rational(cast(byte) 2, cast(byte) 3) == rational(1, 1));
646     assert( rational(1, 3) + rational(1, 2L) == rational(5, 6));
647     auto f5 = rational( BigInt("314159265"), BigInt("27182818"));
648     auto f6 = rational( BigInt("8675309"), BigInt("362436"));
649     f5 += f6;
650     assert(f5 == rational( BigInt("174840986505151"), BigInt("4926015912324")));
651     assert( rational(1, 3) + 2UL == rational(7, 3));
652     assert( 5UL + rational(1, 5) == rational(26, 5));
653
654     // Test subtraction.
655     assert( rational(2, 3) - rational(1, 3) == rational(1, 3UL));
656     assert( rational(1UL, 2) - rational(1, 3) == rational(1, 6));
657     f5 = rational( BigInt("314159265"), BigInt("27182818"));
658     f5 -= f6;
659     assert(f5 == rational( BigInt("-60978359135611"), BigInt("4926015912324")));
660     assert( rational(4, 3) - 1 == rational(1, 3));
661     assert(1 - rational(1, 4) == rational(3, 4));
662
663     // Test unary operators.
664     auto fExp = rational(2, 5);
665     assert(-fExp == rational(-2, 5));
666     assert(+fExp == rational(2, 5));
667
668     // Test exponentiation.
669     fExp ^^= 3;
670     assert(fExp == rational(8, 125));
671     fExp = fExp ^^ 2;
672     assert(fExp == rational(64, 125 * 125));
673     assert(rational(2, 5) ^^ -2 == rational(25, 4));
674
675     // Test decimal conversion.
676     assert(approxEqual(cast(real) f5, -12.37883925284411L));
677
678     // Test comparison.
679     assert(rational(1UL, 6) < rational(1, 2));
680     assert(rational(cast(byte) 1, cast(byte) 2) > rational(1, 6));
681     assert(rational(-1, 7) < rational(7, 2));
682     assert(rational(7, 2) > rational(-1, 7));
683     assert(rational(7, 9) > rational(8, 11));
684     assert(rational(8, 11) < rational(7, 9));
685
686     assert(rational(9, 10) < 1UL);
687     assert(1UL > rational(9, 10));
688     assert(10 > rational(9L, 10));
689     assert(2 > rational(5, 4));
690     assert(1 < rational(5U, 4));
691
692     // Test creating rationals of value zero.
693     auto zero = rational(0, 8);
694     assert(zero == 0);
695     assert(zero == rational(0, 16));
696     assert(zero.num == 0);
697     assert(zero.denom == 1);
698     auto one = zero + 1;
699     one -= one;
700     assert(one == zero);
701
702     // Test integerPart, fraction part.
703     auto intFract = rational(5, 4);
704     assert(intFract.integerPart == 1);
705     assert(intFract.fractionPart == rational(1, 4));
706     assert(cast(long) intFract == 1);
707
708     // Test whether CTFE works for primitive types.  Doesn't work yet.
709 //    enum myRational = (((rational(1, 2) + rational(1, 4)) * 2 - rational(1, 4))
710 //        / 2 + 1 * rational(1, 2) - 1) / rational(2, 5);
711 //    writeln(myRational);
712 //    static assert(myRational == rational(-15, 32));
713 }
714
715 /**Convert a floating point number to a Rational based on integer type Int.
716  * Allows an error tolerance of epsilon.  (Default epsilon = 1e-8.)
717  *
718  * epsilon must be greater than 1.0L / long.max.
719  *
720  * Throws:  Exception on infinities, NaNs, numbers with absolute value
721  * larger than long.max and epsilons smaller than 1.0L / long.max.
722  *
723  * Examples:
724  * ---
725  * // Prints "22 / 7".
726  * writeln( toRational!int( PI, 1e-1));
727  * ---
728  */
729 Rational!(Int) toRational(Int)(real floatNum, real epsilon = 1e-8) {
730     enforce(floatNum != real.infinity && floatNum != -real.infinity
731         && !isNaN(floatNum), "Can't convert NaNs and infinities to rational.");
732     enforce(floatNum < long.max && floatNum > -long.max,
733         "Rational conversions of very large numbers not yet implemented.");
734     enforce(1.0L / epsilon < long.max,
735         "Can't handle very small epsilons < long.max in toRational.");
736
737     // Handle this as a special case to make the rest of the code less
738     // complicated:
739     if( abs(floatNum) < epsilon) {
740         Rational!Int ret;
741         ret.numerator = 0;
742         ret.denominator = 1;
743         return ret;
744     }
745
746     return toRationalImpl!(Int)(floatNum, epsilon);
747 }
748
749 private Rational!Int toRationalImpl(Int)(real floatNum, real epsilon) {
750     real actualEpsilon;
751     Rational!Int ret;
752
753     if( abs(floatNum) < 1) {
754         real invFloatNum = 1.0L / floatNum;
755         long intPart = roundTo!long(invFloatNum);
756         actualEpsilon = floatNum - 1.0L / intPart;
757
758         static if(isIntegral!(Int)) {
759             ret.denominator = cast(Int) intPart;
760             ret.numerator = cast(Int) 1;
761         } else {
762             ret.denominator = intPart;
763             ret.numerator = 1;
764         }
765     } else {
766         long intPart = roundTo!long(floatNum);
767         actualEpsilon = floatNum - intPart;
768
769         static if(isIntegral!(Int)) {
770             ret.denominator = cast(Int) 1;
771             ret.numerator = cast(Int) intPart;
772         } else {
773             ret.denominator = 1;
774             ret.numerator = intPart;
775         }
776     }
777
778     if(abs(actualEpsilon) <= epsilon) {
779         return ret;
780     }
781
782     // Else get results from downstream recursions, add them to this result.
783     return ret + toRationalImpl!(Int)(actualEpsilon, epsilon);
784 }
785
786 unittest {
787     // Start with simple cases.
788     assert( toRational!int(0.5) == rational(1, 2));
789     assert( toRational!BigInt(0.333333333333333L) ==
790         rational( BigInt(1), BigInt(3)));
791     assert( toRational!int(2.470588235294118) ==
792         rational( cast(int) 42, cast(int) 17));
793     assert( toRational!long(2.007874015748032) == rational(255L, 127L));
794     assert( toRational!int( 3.0L / 7.0L) == rational(3, 7));
795     assert( toRational!int( 7.0L / 3.0L) == rational(7, 3));
796
797     // Now for some fun.
798     real myEpsilon = 1e-8;
799     auto piRational = toRational!long(PI, myEpsilon);
800     assert( abs( cast(real) piRational - PI) < myEpsilon);
801
802     auto eRational = toRational!long(E, myEpsilon);
803     assert( abs( cast(real) eRational - E) < myEpsilon);
804 }
805
806
807 /**Find the greatest common factor of num1 and num2 using Euclid's Algorithm.*/
808 CommonInteger!(I1, I2) gcf(I1, I2)(I1 num1, I2 num2)
809 if(isIntegerLike!I1 && isIntegerLike!I2) {
810     num1 = iAbs(num1);
811     num2 = iAbs(num2);
812     if(num2 > num1) {
813         return gcf(num2, num1);
814     } else if(num2 == num1) {
815         typeof(return) ret = num1;
816         return ret;
817     }
818
819     // Work around Bug 4742.
820     static if(is(I1 == I2)) {
821         auto remainder = num1 % num2;
822     } else {
823         typeof(return) workaround1 = num1;
824         typeof(return) workaround2 = num2;
825         auto remainder = workaround1 % workaround2;
826     }
827
828     if(remainder == 0) {
829         typeof(return) ret = num2;
830         return ret;
831     } else {
832         return gcf(num2, remainder);
833     }
834     assert(0);
835 }
836
837 unittest {
838     // Values from the Maxima computer algebra system.
839     assert(gcf( BigInt(314_156_535UL), BigInt(27_182_818_284UL)) == BigInt(3));
840     assert(gcf(8675309, 362436) == 1);
841     assert(gcf( BigInt("8589934596"), BigInt("295147905179352825852")) ==
842         12);
843 }
844
845 /**Find the least common multiple of num1, num2.*/
846 CommonInteger!(I1, I2) lcm(I1, I2)(I1 num1, I2 num2)
847 if(isIntegerLike!I1 && isIntegerLike!I2) {
848     num1 = iAbs(num1);
849     num2 = iAbs(num2);
850     if(num1 == num2) {
851         return num1;
852     }
853     return (num1 / gcf(num1, num2)) * num2;
854 }
855
856 /**Absolute value function that should gracefully handle any reasonable
857  * BigInt implementation.*/
858 Int iAbs(Int)(Int num1)
859 if(isIntegerLike!Int) {
860     static if(isUnsigned!Int) {
861         return num1;
862     } else {
863         // For some reason DMD insists that a byte multipled by -1 is an int
864         // not a byte.
865         return cast(Int) ((num1 < 0) ? -1 * num1 : num1);
866     }
867 }
868
869 /**Returns the largest integer less than or equal to $(D r).*/
870 Int floor(Int)(Rational!Int r) {
871     auto intPart = r.integerPart;
872     if(r > 0 || intPart == r) {
873         return intPart;
874     } else {
875         intPart -= 1;
876         return intPart;
877     }
878 }
879
880 unittest {
881     assert(floor(rational(1, 2)) == 0);
882     assert(floor(rational(-1, 2)) == -1);
883     assert(floor(rational(2)) == 2);
884     assert(floor(rational(-2)) == -2);
885     assert(floor(rational(-1, 2)) == -1);
886 }
887
888 /**Returns the smallest integer greater than or equal to $(D r).*/
889 Int ceil(Int)(Rational!Int r) {
890     auto intPart = r.integerPart;
891     if(intPart == r || r < 0) {
892         return intPart;
893     } else {
894         intPart += 1;
895         return intPart;
896     }
897 }
898
899 unittest {
900     assert(ceil(rational(1, 2)) == 1);
901     assert(ceil(rational(0)) == 0);
902     assert(ceil(rational(-1, 2)) == 0);
903     assert(ceil(rational(1)) == 1);
904     assert(ceil(rational(-2)) == -2);
905 }
906
907 /**Round $(D r) to the nearest integer.  If the fractional part is exactly
908  * 1 / 2, $(D r) will be rounded such that the absolute value is increased by
909  * rounding.
910  */
911 Int round(Int)(Rational!Int r) {
912     auto intPart = r.integerPart;
913     auto fractPart = r.fractionPart;
914
915     bool added;
916     if(fractPart >= rational(1, 2)) {
917         added = true;
918         intPart += 1;
919     }
920
921     static if(!isUnsigned!Int) {
922         if(!added && fractPart <= rational(-1, 2)) {
923             intPart -= 1;
924         }
925     }
926
927     return intPart;
928 }
929
930 unittest {
931     assert(round(rational(1, 3)) == 0);
932     assert(round(rational(7, 2)) == 4);
933     assert(round(rational(-3, 4)) == -1);
934     assert(round(rational(8U, 15U)) == 1);
935 }
936
937 version(unittest) {
938     void main() {}
939 }
Note: See TracBrowser for help on using the browser.