1 /++ 2 Structs used to define units: raional numbers and dimensions. 3 4 Copyright: Copyright 2013-2018, Nicolas Sicard 5 Authors: Nicolas Sicard 6 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 Source: $(LINK https://github.com/biozic/quantities) 8 +/ 9 module quantities.internal.dimensions; 10 11 import std.algorithm; 12 import std.array; 13 import std.conv; 14 import std.exception; 15 import std.format; 16 import std.math; 17 import std.string; 18 import std.traits; 19 20 /// Reduced implementation of a rational number 21 struct Rational 22 { 23 private: 24 int num = 0; 25 int den = 1; 26 27 invariant() 28 { 29 assert(den != 0); 30 } 31 32 void normalize() @safe pure nothrow 33 { 34 if (den < 0) 35 { 36 num = -num; 37 den = -den; 38 } 39 immutable g = gcd(num, den); 40 num /= g; 41 den /= g; 42 } 43 44 private bool isNormalized() @safe pure nothrow const 45 { 46 return den >= 0 && gcd(num, den) == 1; 47 } 48 49 public: 50 /++ 51 Create a rational number. 52 53 Params: 54 num = The numerator 55 den = The denominator 56 +/ 57 this(int num, int den = 1) @safe pure nothrow 58 { 59 this.num = num; 60 this.den = den; 61 normalize(); 62 } 63 64 void opOpAssign(string op)(Rational other) @safe pure nothrow 65 if (op == "+" || op == "-" || op == "*" || op == "/") 66 { 67 mixin("this = this" ~ op ~ "other;"); 68 assert(isNormalized); 69 } 70 71 void opOpAssign(string op)(int value) @safe pure nothrow 72 if (op == "+" || op == "-" || op == "*" || op == "/") 73 { 74 mixin("this = this" ~ op ~ "value;"); 75 assert(isNormalized); 76 } 77 78 Rational opUnary(string op)() @safe pure nothrow const 79 if (op == "+" || op == "-") 80 out (result) 81 { 82 assert(result.isNormalized); 83 } 84 body 85 { 86 return Rational(mixin(op ~ "num"), den); 87 } 88 89 Rational opBinary(string op)(Rational other) @safe pure nothrow const 90 if (op == "+" || op == "-") 91 { 92 auto ret = Rational(mixin("num * other.den" ~ op ~ "other.num * den"), den * other.den); 93 ret.normalize(); 94 return ret; 95 } 96 97 Rational opBinary(string op)(Rational other) @safe pure nothrow const 98 if (op == "*") 99 { 100 auto ret = Rational(num * other.num, den * other.den); 101 ret.normalize(); 102 return ret; 103 } 104 105 Rational opBinary(string op)(Rational other) @safe pure nothrow const 106 if (op == "/") 107 { 108 auto ret = Rational(num * other.den, den * other.num); 109 ret.normalize(); 110 return ret; 111 } 112 113 Rational opBinary(string op)(int value) @safe pure nothrow const 114 if (op == "+" || op == "-" || op == "*" || op == "/") 115 out 116 { 117 assert(isNormalized); 118 } 119 body 120 { 121 return mixin("this" ~ op ~ "Rational(value)"); 122 } 123 124 bool opEquals(Rational other) @safe pure nothrow const 125 { 126 return num == other.num && den == other.den; 127 } 128 129 bool opEquals(int value) @safe pure nothrow const 130 { 131 return num == value && den == 1; 132 } 133 134 int opCmp(Rational other) @safe pure nothrow const 135 { 136 immutable diff = (num / cast(double) den) - (other.num / cast(double) other.den); 137 if (diff == 0) 138 return 0; 139 if (diff > 0) 140 return 1; 141 return -1; 142 } 143 144 T opCast(T)() @safe pure nothrow const 145 if (isNumeric!T) 146 { 147 return cast(T) num / cast(T) den; 148 } 149 150 /// 151 void toString(scope void delegate(const(char)[]) sink) const 152 { 153 sink.formattedWrite!"%d"(num); 154 if (den != 1) 155 { 156 sink("/"); 157 sink.formattedWrite!"%d"(den); 158 } 159 } 160 } 161 162 private int gcd(int x, int y) @safe pure nothrow 163 { 164 if (x == 0 || y == 0) 165 return 1; 166 167 int tmp; 168 int a = abs(x); 169 int b = abs(y); 170 while (a > 0) 171 { 172 tmp = a; 173 a = b % a; 174 b = tmp; 175 } 176 return b; 177 } 178 179 unittest 180 { 181 const r = Rational(6, -8); 182 assert(r.text == "-3/4"); 183 assert((+r).text == "-3/4"); 184 assert((-r).text == "3/4"); 185 186 const r1 = Rational(4, 3) + Rational(2, 5); 187 assert(r1.text == "26/15"); 188 const r2 = Rational(4, 3) - Rational(2, 5); 189 assert(r2.text == "14/15"); 190 const r3 = Rational(8, 7) * Rational(3, -2); 191 assert(r3.text == "-12/7"); 192 const r4 = Rational(8, 7) / Rational(3, -2); 193 assert(r4.text == "-16/21"); 194 195 auto r5 = Rational(4, 3); 196 r5 += Rational(2, 5); 197 assert(r5.text == "26/15"); 198 199 auto r6 = Rational(8, 7); 200 r6 /= Rational(2, -3); 201 assert(r6.text == "-12/7"); 202 203 assert(Rational(8, 7) == Rational(-16, -14)); 204 assert(Rational(2, 5) < Rational(3, 7)); 205 } 206 207 /// Struct describind properties of a dimension in a dimension vector. 208 struct Dim 209 { 210 string symbol; /// The symbol of the dimension 211 Rational power; /// The power of the dimension 212 213 this(string symbol, Rational power) @safe pure nothrow 214 { 215 this.symbol = symbol; 216 this.power = power; 217 } 218 219 this(string symbol, int power) @safe pure nothrow 220 { 221 this(symbol, Rational(power)); 222 } 223 224 int opCmp(Dim other) @safe pure nothrow const 225 { 226 if (symbol < other.symbol) 227 return -1; 228 else if (symbol > other.symbol) 229 return 1; 230 else 231 return 0; 232 } 233 234 /// 235 void toString(scope void delegate(const(char)[]) sink) const 236 { 237 if (power == 0) 238 return; 239 if (power == 1) 240 sink(symbol); 241 else 242 { 243 sink.formattedWrite!"%s"(symbol); 244 sink("^"); 245 sink.formattedWrite!"%s"(power); 246 } 247 } 248 } 249 250 private immutable(Dim)[] inverted(immutable(Dim)[] source) @safe pure nothrow 251 { 252 Dim[] target = source.dup; 253 foreach (ref dim; target) 254 dim.power = -dim.power; 255 return target.immut; 256 } 257 258 @safe pure nothrow unittest 259 { 260 auto list = [Dim("A", 2), Dim("B", -2)].idup; 261 auto inv = [Dim("A", -2), Dim("B", 2)].idup; 262 assert(list.inverted == inv); 263 } 264 265 private void insertAndSort(ref Dim[] list, string symbol, Rational power) @safe pure nothrow 266 { 267 auto pos = list.countUntil!(d => d.symbol == symbol)(); 268 if (pos >= 0) 269 { 270 // Merge the dimensions 271 list[pos].power += power; 272 if (list[pos].power == 0) 273 { 274 try 275 list = list.remove(pos); 276 catch (Exception) // remove only throws when it has multiple arguments 277 assert(false); 278 279 // Necessary to compare dimensionless values 280 if (!list.length) 281 list = null; 282 } 283 } 284 else 285 { 286 // Insert the new dimension 287 pos = list.countUntil!(d => d.symbol > symbol)(); 288 if (pos < 0) 289 pos = list.length; 290 list.insertInPlace(pos, Dim(symbol, power)); 291 } 292 //assert(list.isSorted); 293 } 294 295 @safe pure nothrow unittest 296 { 297 Dim[] list; 298 list.insertAndSort("A", Rational(1)); 299 assert(list == [Dim("A", 1)]); 300 list.insertAndSort("A", Rational(1)); 301 assert(list == [Dim("A", 2)]); 302 list.insertAndSort("A", Rational(-2)); 303 assert(list.length == 0); 304 } 305 306 private immutable(Dim)[] immut(Dim[] source) @trusted pure nothrow 307 { 308 if (__ctfe) 309 return source.idup; 310 else 311 return source.assumeUnique; 312 } 313 314 private immutable(Dim)[] insertSorted(immutable(Dim)[] source, string symbol, Rational power) @safe pure nothrow 315 { 316 if (power == 0) 317 return source; 318 319 if (!source.length) 320 return [Dim(symbol, power)].immut; 321 322 Dim[] list = source.dup; 323 insertAndSort(list, symbol, power); 324 return list.immut; 325 } 326 327 private immutable(Dim)[] insertSorted(immutable(Dim)[] source, immutable(Dim)[] other) @safe pure nothrow 328 { 329 Dim[] list = source.dup; 330 foreach (dim; other) 331 insertAndSort(list, dim.symbol, dim.power); 332 return list.immut; 333 } 334 335 /// A vector of dimensions 336 struct Dimensions 337 { 338 private: 339 immutable(Dim)[] _dims; 340 341 package(quantities): 342 static Dimensions mono(string symbol) @safe pure nothrow 343 { 344 if (!symbol.length) 345 return Dimensions(null); 346 return Dimensions([Dim(symbol, 1)].immut); 347 } 348 349 public: 350 this(this) @safe pure nothrow 351 { 352 _dims = _dims.idup; 353 } 354 355 ref Dimensions opAssign()(auto ref const Dimensions other) @safe pure nothrow 356 { 357 _dims = other._dims.idup; 358 return this; 359 } 360 361 /// The dimensions stored in this vector 362 immutable(Dim)[] dims() @safe pure nothrow const 363 { 364 return _dims; 365 } 366 367 /// Test whether the dimension vector is empty. 368 bool empty() @safe pure nothrow const 369 { 370 return _dims.empty; 371 } 372 373 Dimensions opUnary(string op)() @safe pure nothrow const 374 if (op == "~") 375 { 376 return Dimensions(_dims.inverted); 377 } 378 379 Dimensions opBinary(string op)(const Dimensions other) @safe pure nothrow const 380 if (op == "*") 381 { 382 return Dimensions(_dims.insertSorted(other._dims)); 383 } 384 385 @safe pure nothrow unittest 386 { 387 auto dim1 = Dimensions([Dim("a", 1), Dim("b", -2)]); 388 auto dim2 = Dimensions([Dim("a", -1), Dim("c", 2)]); 389 assert(dim1 * dim2 == Dimensions([Dim("b", -2), Dim("c", 2)])); 390 } 391 392 Dimensions opBinary(string op)(const Dimensions other) @safe pure nothrow const 393 if (op == "/") 394 { 395 return Dimensions(_dims.insertSorted(other._dims.inverted)); 396 } 397 398 @safe pure nothrow unittest 399 { 400 auto dim1 = Dimensions([Dim("a", 1), Dim("b", -2)]); 401 auto dim2 = Dimensions([Dim("a", 1), Dim("c", 2)]); 402 assert(dim1 / dim2 == Dimensions([Dim("b", -2), Dim("c", -2)])); 403 } 404 405 Dimensions pow(Rational n) @safe pure nothrow const 406 { 407 if (n == 0) 408 return Dimensions.init; 409 410 auto list = _dims.dup; 411 foreach (ref dim; list) 412 dim.power = dim.power * n; 413 return Dimensions(list.immut); 414 } 415 416 Dimensions pow(int n) @safe pure nothrow const 417 { 418 return pow(Rational(n)); 419 } 420 421 @safe pure nothrow unittest 422 { 423 auto dim = Dimensions([Dim("a", 5), Dim("b", -2)]); 424 assert(dim.pow(Rational(2)) == Dimensions([Dim("a", 10), Dim("b", -4)])); 425 assert(dim.pow(Rational(0)) == Dimensions.init); 426 } 427 428 Dimensions powinverse(Rational n) @safe pure nothrow const 429 { 430 import std.exception : enforce; 431 import std.string : format; 432 433 auto list = _dims.dup; 434 foreach (ref dim; list) 435 dim.power = dim.power / n; 436 return Dimensions(list.immut); 437 } 438 439 Dimensions powinverse(int n) @safe pure nothrow const 440 { 441 return powinverse(Rational(n)); 442 } 443 444 @safe pure nothrow unittest 445 { 446 auto dim = Dimensions([Dim("a", 6), Dim("b", -2)]); 447 assert(dim.powinverse(Rational(2)) == Dimensions([Dim("a", 3), Dim("b", -1)])); 448 } 449 450 /// 451 void toString(scope void delegate(const(char)[]) sink) const 452 { 453 sink.formattedWrite!"[%(%s %)]"(_dims); 454 } 455 456 unittest 457 { 458 auto dim = Dimensions([Dim("a", 1), Dim("b", -2)]); 459 assert(dim.text == "[a b^-2]"); 460 } 461 } 462 463 @safe pure nothrow unittest // Compile-time 464 { 465 enum a = Dimensions([Dim("a", 1)]); 466 enum b = Dimensions([Dim("b", 2)]); 467 enum c = a / b; 468 static assert(c.text == "[a b^-2]"); 469 }