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 23 { 24 opAssign!long(n); 25 } 26 27 /// Construct a Rational from another Rational. 28 this(Rational f) pure nothrow 29 { 30 opAssign!Rational(f); 31 } 32 33 /// Construct a Rational from numerator and denominator. 34 this(long numerator, long denominator) pure nothrow 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 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 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 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 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 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 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 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 if (op=="++" || op=="--") 126 { 127 static if (op=="++") 128 { 129 num += denom; 130 debug checkInvariant(); // should still be reduced 131 } 132 else static if (op=="--") 133 { 134 num -= denom; 135 debug checkInvariant(); // should still be reduced 136 } 137 return this; 138 } 139 140 bool opEquals(T)(T y) pure const if (!is(Unqual!T == Rational)) 141 { 142 return this == Rational(y); 143 } 144 145 bool opEquals(T)(T o) pure const 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 int opCmp(T)(T o) pure const if (!is(Unqual!T == Rational)) 152 { 153 return opCmp(Rational(o)); 154 } 155 156 int opCmp(T)(T o) pure const 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 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 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 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 debug checkInvariant(); 198 } 199 200 void checkInvariant() pure nothrow // can't do this in invariant() because of opAssign 201 { 202 assert(denom > 0); 203 auto gcd = GCD(num, denom); 204 assert(gcd == 1 || gcd == -1); 205 } 206 } 207 } 208 209 unittest 210 { 211 Rational x = Rational(9, 3); 212 assert(x.num == 3); 213 assert(x.denom == 1); 214 215 assert(x < 4); 216 assert(x > 2); 217 assert(x > Rational(8,3)); 218 assert(x > Rational(-8,3)); 219 assert(x == Rational(-27, -9)); 220 221 assert(Rational(-4, 7) + 2 == Rational(10, 7)); 222 assert(Rational(-4, 7) == Rational(10, 7) - 2); 223 224 assert(++Rational(3,7) == Rational(10,7)); 225 assert(--Rational(3,7) == Rational(-4,7)); 226 assert(+x == 3); 227 assert(-x == -3); 228 229 Rational y = -4; 230 assert(y.num == -4); 231 assert(y.denom == 1); 232 233 assert(Rational(2, 3) * Rational(15, 7) == Rational(10, 7)); 234 assert(Rational(2, 3) == Rational(10, 7) / Rational(15, 7)); 235 }