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 }