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 }