1 module gfm.math.rational;
2 
3 import std.traits,
4        std..string;
5 
6 /**
7   
8   A rational number, always in reduced form. Supports basic arithmetic.
9 
10   Bugs: Remove this module once std.rational is here.
11 
12  */
13 align(1) struct Rational
14 {
15     align(1):
16     public
17     {
18         long num;   /// Numerator.
19         long denom; /// Denominator.
20 
21         /// Construct a Rational from an integer.
22         this(long n) pure nothrow @nogc 
23         {
24             opAssign!long(n);
25         }
26 
27         /// Construct a Rational from another Rational.
28         this(Rational f) pure nothrow @nogc 
29         {
30             opAssign!Rational(f);
31         }
32 
33         /// Construct a Rational from numerator and denominator.
34         this(long numerator, long denominator) pure nothrow @nogc 
35         {
36             num = numerator;
37             denom = denominator;
38             reduce();
39         }
40 
41         /// Converts to pretty string.
42         string toString() const 
43         {
44             return format("%s/%s", num, denom);
45         }
46 
47         /// Cast to floating point.
48         T opCast(T)() pure const nothrow @nogc if (isFloatingPoint!T)
49         {
50             return cast(T)num / cast(T)denom;
51         }
52 
53         /// Assign with another Rational.
54         ref Rational opAssign(T)(T other) pure nothrow @nogc if (is(Unqual!T == Rational))
55         {
56             num = other.num;
57             denom = other.denom;
58             return this;
59         }
60 
61         /// Assign with an integer.
62         ref Rational opAssign(T)(T n) pure nothrow @nogc if (isIntegral!T)
63         {
64             num = n;
65             denom = 1;
66             return this;
67         }
68 
69         Rational opBinary(string op, T)(T o) pure const nothrow @nogc 
70         {
71             Rational r = this;
72             Rational y = o;
73             return r.opOpAssign!(op)(y);
74         }
75 
76         ref Rational opOpAssign(string op, T)(T o) pure nothrow @nogc if (!is(Unqual!T == Rational))
77         {
78             const(self) o = y;
79             return opOpAssign!(op)(o);
80         }
81 
82         ref Rational opOpAssign(string op, T)(T o) pure nothrow @nogc if (is(Unqual!T == Rational))
83         {
84             static if (op == "+")
85             {
86                 num = num * o.denom + o.num * denom;
87                 denom = denom * o.denom;
88                 reduce();
89             }
90             else static if (op == "-")
91             {
92                 opOpAssign!"+"(-o);
93             }
94             else static if (op == "*")
95             {
96                 denom = denom * o.denom;
97                 num = num * o.num;
98                 reduce();
99             }
100             else static if (op == "/")
101             {
102                 opOpAssign!"*"(o.inverse());
103             }
104             else
105             {
106                 static assert(false, "unsupported operation '" ~ op ~ "'");
107             }
108             return this;
109         }
110 
111         // const unary operations
112         Rational opUnary(string op)() pure const nothrow @nogc if (op == "+" || op == "-")
113         {
114             static if (op == "-")
115             {
116                 Rational f = this;
117                 f.num = -f.num;
118                 return f;
119             }
120             else static if (op == "+")
121                 return this;
122         }
123 
124         // non-const unary operations
125         Rational opUnary(string op)() pure nothrow @nogc if (op=="++" || op=="--")
126         {
127             static if (op=="++")
128             {
129                 num += denom;
130             }
131             else static if (op=="--")
132             {
133                 num -= denom;
134             }
135             return this;
136         }
137 
138         bool opEquals(T)(T y) pure const nothrow @nogc if (!is(Unqual!T == Rational))
139         {
140             return this == Rational(y);
141         }
142 
143         bool opEquals(T)(T o) pure const nothrow @nogc if (is(Unqual!T == Rational))
144         {
145             // invariants ensures two equal Rationals have equal representations
146             return num == o.num && denom == o.denom;
147         }
148 
149         int opCmp(T)(T o) pure const @nogc if (!is(Unqual!T == Rational))
150         {
151             return opCmp(Rational(o));
152         }
153 
154         int opCmp(T)(T o) pure const nothrow @nogc if (is(Unqual!T == Rational))
155         {
156             assert(denom > 0);
157             assert(o.denom > 0);
158             long det = num * o.denom - denom * o.num;
159             if (det > 0)
160                 return 1;
161             else if (det < 0)
162                 return -1;
163             else
164                 return 0;
165         }
166 
167         /// Returns: Inverse of this rational number.
168         Rational inverse() pure const nothrow @nogc
169         {
170             return Rational(denom, num);
171         }
172     }
173 
174     private
175     {
176         // FIXME: be consistent with regards to sign
177         static long GCD(long a, long b) pure nothrow @nogc
178         {
179             if (b == 0)
180                 return a;
181             else
182                 return GCD(b, a % b);
183         }
184 
185         void reduce() pure nothrow @nogc
186         {
187             const(long) gcd = GCD(num, denom);
188             num /= gcd;
189             denom /= gcd;
190             if (denom < 0)
191             {
192                 num = -num;
193                 denom = -denom;
194             }
195         }
196     }
197 }
198 
199 unittest
200 {
201     Rational x = Rational(9, 3);
202     assert(x.num == 3);
203     assert(x.denom == 1);
204 
205     assert(x < 4);
206     assert(x > 2);
207     assert(x > Rational(8,3));
208     assert(x > Rational(-8,3));
209     assert(x == Rational(-27, -9));
210 
211     assert(Rational(-4, 7) + 2 == Rational(10, 7));
212     assert(Rational(-4, 7) == Rational(10, 7) - 2);
213 
214     assert(++Rational(3,7) == Rational(10,7));
215     assert(--Rational(3,7) == Rational(-4,7));
216     assert(+x == 3);
217     assert(-x == -3);
218 
219     Rational y = -4;
220     assert(y.num == -4);
221     assert(y.denom == 1);
222 
223     assert(Rational(2, 3) * Rational(15, 7) == Rational(10, 7));
224     assert(Rational(2, 3) == Rational(10, 7) / Rational(15, 7));
225 }