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