1 /++ 2 This module defines quantities that are statically checked for dimensional 3 consistency at compile-time. 4 5 The dimensions are part of their types, so that the compilation fails if an 6 operation or a function call is not dimensionally consistent. 7 8 Copyright: Copyright 2013-2018, Nicolas Sicard 9 Authors: Nicolas Sicard 10 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 11 Source: $(LINK https://github.com/biozic/quantities) 12 +/ 13 module quantities.compiletime; 14 15 /// 16 unittest 17 { 18 import quantities.compiletime; 19 import quantities.si; 20 import std.format : format; 21 import std.math : approxEqual; 22 23 // Introductory example 24 { 25 // Use the predefined quantity types (in module quantities.si) 26 Volume volume; 27 Concentration concentration; 28 Mass mass; 29 30 // Define a new quantity type 31 alias MolarMass = typeof(kilogram / mole); 32 33 // I have to make a new solution at the concentration of 5 mmol/L 34 concentration = 5.0 * milli(mole) / liter; 35 36 // The final volume is 100 ml. 37 volume = 100.0 * milli(liter); 38 39 // The molar mass of my compound is 118.9 g/mol 40 MolarMass mm = 118.9 * gram / mole; 41 42 // What mass should I weigh? 43 mass = concentration * volume * mm; 44 assert(format("%s", mass) == "5.945e-05 [M]"); 45 // Wait! That's not really useful! 46 assert(siFormat!"%.1f mg"(mass) == "59.5 mg"); 47 } 48 49 // Working with predefined units 50 { 51 auto distance = 384_400 * kilo(meter); // From Earth to Moon 52 auto speed = 299_792_458 * meter / second; // Speed of light 53 auto time = distance / speed; 54 assert(time.siFormat!"%.3f s" == "1.282 s"); 55 } 56 57 // Dimensional correctness is check at compile-time 58 { 59 Mass mass; 60 assert(!__traits(compiles, mass = 15 * meter)); 61 assert(!__traits(compiles, mass = 1.2)); 62 } 63 64 // Calculations can be done at compile-time 65 { 66 enum distance = 384_400 * kilo(meter); // From Earth to Moon 67 enum speed = 299_792_458 * meter / second; // Speed of light 68 enum time = distance / speed; 69 /* static */ 70 assert(time.siFormat!"%.3f s" == "1.282 s"); 71 // NB. Phobos can't format floating point values at run-time. 72 } 73 74 // Create a new unit from the predefined ones 75 { 76 auto inch = 2.54 * centi(meter); 77 auto mile = 1609 * meter; 78 assert(mile.value(inch).approxEqual(63_346)); // inches in a mile 79 // NB. Cannot use siFormatter, because inches are not SI units 80 } 81 82 // Create a new unit with new dimensions 83 { 84 // Create a new base unit of currency 85 auto euro = unit!(double, "C"); // C is the chosen dimension symol (for currency...) 86 87 auto dollar = euro / 1.35; 88 auto price = 2000 * dollar; 89 assert(price.value(euro).approxEqual(1481)); // Price in euros 90 } 91 92 // Compile-time parsing 93 { 94 enum distance = si!"384_400 km"; 95 enum speed = si!"299_792_458 m/s"; 96 assert(is(typeof(distance) == Length)); 97 assert(is(typeof(speed) == Speed)); 98 } 99 100 // Run-time parsing of statically typed Quantities 101 { 102 auto data = ["distance-to-the-moon" : "384_400 km", "speed-of-light" : "299_792_458 m/s"]; 103 auto distance = parseSI!Length(data["distance-to-the-moon"]); 104 auto speed = parseSI!Speed(data["speed-of-light"]); 105 } 106 } 107 108 import quantities.internal.dimensions; 109 import quantities.common; 110 import quantities.runtime; 111 import std.format; 112 import std.math; 113 import std.traits : isNumeric, isIntegral; 114 115 /++ 116 A quantity checked at compile-time for dimensional consistency. 117 118 Params: 119 N = the numeric type of the quantity. 120 121 See_Also: 122 QVariant has the same public members and overloaded operators as Quantity. 123 +/ 124 struct Quantity(N, alias dims) 125 { 126 static assert(isNumeric!N); 127 static assert(is(typeof(dims) : Dimensions)); 128 static assert(Quantity.sizeof == N.sizeof); 129 130 private: 131 N _value; 132 133 // Creates a new quantity with non-empty dimensions 134 static Quantity make(T)(T scalar) 135 if (isNumeric!T) 136 { 137 Quantity result; 138 result._value = scalar; 139 return result; 140 } 141 142 void ensureSameDim(const Dimensions d)() const 143 { 144 static assert(dimensions == d, 145 "Dimension error: %s is not consistent with %s".format(dimensions, d)); 146 } 147 148 void ensureEmpty(const Dimensions d)() const 149 { 150 static assert(d.empty, "Dimension error: %s instead of no dimensions".format(d)); 151 } 152 153 package(quantities): 154 alias valueType = N; 155 156 N rawValue() const 157 { 158 return _value; 159 } 160 161 public: 162 /++ 163 Creates a new quantity from another one with the same dimensions. 164 165 If Q is a QVariant, throws a DimensionException if the parsed quantity 166 doesn't have the same dimensions as Q. If Q is a Quantity, inconsistent 167 dimensions produce a compilation error. 168 +/ 169 this(Q)(auto ref const Q qty) 170 if (isQuantity!Q) 171 { 172 ensureSameDim!(Q.dimensions); 173 _value = qty._value; 174 } 175 176 /// ditto 177 this(Q)(auto ref const Q qty) 178 if (isQVariant!Q) 179 { 180 import std.exception; 181 182 enforce(dimensions == qty.dimensions, 183 new DimensionException("Incompatible dimensions", dimensions, qty.dimensions)); 184 _value = qty.rawValue; 185 } 186 187 /// Creates a new dimensionless quantity from a number 188 this(T)(T scalar) 189 if (isNumeric!T && isDimensionless) 190 { 191 _value = scalar; 192 } 193 194 /// The dimensions of the quantity 195 enum dimensions = dims; 196 197 /++ 198 Implicitly convert a dimensionless value to the value type. 199 +/ 200 static if (isDimensionless) 201 { 202 N get() const 203 { 204 return _value; 205 } 206 207 alias get this; 208 } 209 210 /++ 211 Gets the _value of this quantity when expressed in the given target unit. 212 213 If Q is a QVariant, throws a DimensionException if the parsed quantity 214 doesn't have the same dimensions as Q. If Q is a Quantity, inconsistent 215 dimensions produce a compilation error. 216 +/ 217 N value(Q)(auto ref const Q target) const 218 if (isQuantity!Q) 219 { 220 mixin ensureSameDim!(Q.dimensions); 221 return _value / target._value; 222 } 223 224 /// ditto 225 N value(Q)(auto ref const Q target) const 226 if (isQVariant!Q) 227 { 228 import std.exception; 229 230 enforce(dimensions == target.dimensions, 231 new DimensionException("Incompatible dimensions", dimensions, target.dimensions)); 232 return _value / target.rawValue; 233 } 234 235 /++ 236 Test whether this quantity is dimensionless 237 +/ 238 enum bool isDimensionless = dimensions.length == 0; 239 240 /++ 241 Tests wheter this quantity has the same dimensions as another one. 242 +/ 243 bool isConsistentWith(Q)(auto ref const Q qty) const 244 if (isQVariantOrQuantity!Q) 245 { 246 return dimensions == qty.dimensions; 247 } 248 249 /++ 250 Returns the base unit of this quantity. 251 +/ 252 Quantity baseUnit() @property const 253 { 254 return Quantity.make(1); 255 } 256 257 /++ 258 Cast a dimensionless quantity to a numeric type. 259 260 The cast operation will throw DimensionException if the quantity is not 261 dimensionless. 262 +/ 263 static if (isDimensionless) 264 { 265 T opCast(T)() const 266 if (isNumeric!T) 267 { 268 return _value; 269 } 270 } 271 272 // Assign from another quantity 273 /// Operator overloading 274 ref Quantity opAssign(Q)(auto ref const Q qty) 275 if (isQuantity!Q) 276 { 277 ensureSameDim!(Q.dimensions); 278 _value = qty._value; 279 return this; 280 } 281 282 /// ditto 283 ref Quantity opAssign(Q)(auto ref const Q qty) 284 if (isQVariant!Q) 285 { 286 import std.exception; 287 288 enforce(dimensions == qty.dimensions, 289 new DimensionException("Incompatible dimensions", dimensions, qty.dimensions)); 290 _value = qty.rawValue; 291 return this; 292 } 293 294 // Assign from a numeric value if this quantity is dimensionless 295 /// ditto 296 ref Quantity opAssign(T)(T scalar) 297 if (isNumeric!T) 298 { 299 ensureEmpty!dimensions; 300 _value = scalar; 301 return this; 302 } 303 304 // Unary + and - 305 /// ditto 306 Quantity opUnary(string op)() const 307 if (op == "+" || op == "-") 308 { 309 return Quantity.make(mixin(op ~ "_value")); 310 } 311 312 // Unary ++ and -- 313 /// ditto 314 Quantity opUnary(string op)() 315 if (op == "++" || op == "--") 316 { 317 mixin(op ~ "_value;"); 318 return this; 319 } 320 321 // Add (or substract) two quantities if they share the same dimensions 322 /// ditto 323 Quantity opBinary(string op, Q)(auto ref const Q qty) const 324 if (isQuantity!Q && (op == "+" || op == "-")) 325 { 326 ensureSameDim!(Q.dimensions); 327 return Quantity.make(mixin("_value" ~ op ~ "qty._value")); 328 } 329 330 // Add (or substract) a dimensionless quantity and a number 331 /// ditto 332 Quantity opBinary(string op, T)(T scalar) const 333 if (isNumeric!T && (op == "+" || op == "-")) 334 { 335 ensureEmpty!dimensions; 336 return Quantity.make(mixin("_value" ~ op ~ "scalar")); 337 } 338 339 /// ditto 340 Quantity opBinaryRight(string op, T)(T scalar) const 341 if (isNumeric!T && (op == "+" || op == "-")) 342 { 343 ensureEmpty!dimensions; 344 return Quantity.make(mixin("scalar" ~ op ~ "_value")); 345 } 346 347 // Multiply or divide a quantity by a number 348 /// ditto 349 Quantity opBinary(string op, T)(T scalar) const 350 if (isNumeric!T && (op == "*" || op == "/" || op == "%")) 351 { 352 return Quantity.make(mixin("_value" ~ op ~ "scalar")); 353 } 354 355 /// ditto 356 Quantity opBinaryRight(string op, T)(T scalar) const 357 if (isNumeric!T && op == "*") 358 { 359 return Quantity.make(mixin("scalar" ~ op ~ "_value")); 360 } 361 362 /// ditto 363 auto opBinaryRight(string op, T)(T scalar) const 364 if (isNumeric!T && (op == "/" || op == "%")) 365 { 366 alias RQ = Quantity!(N, dimensions.inverted()); 367 return RQ.make(mixin("scalar" ~ op ~ "_value")); 368 } 369 370 // Multiply or divide two quantities 371 /// ditto 372 auto opBinary(string op, Q)(auto ref const Q qty) const 373 if (isQuantity!Q && (op == "*" || op == "/")) 374 { 375 alias RQ = Quantity!(N, mixin("dimensions" ~ op ~ "Q.dimensions")); 376 return RQ.make(mixin("_value" ~ op ~ "qty._value")); 377 } 378 379 /// ditto 380 Quantity opBinary(string op, Q)(auto ref const Q qty) const 381 if (isQuantity!Q && (op == "%")) 382 { 383 ensureSameDim!(Q.dimensions); 384 return Quantity.make(mixin("_value" ~ op ~ "qty._value")); 385 } 386 387 // Add/sub assign with a quantity that shares the same dimensions 388 /// ditto 389 void opOpAssign(string op, Q)(auto ref const Q qty) 390 if (isQuantity!Q && (op == "+" || op == "-")) 391 { 392 ensureSameDim!(Q.dimensions); 393 mixin("_value " ~ op ~ "= qty._value;"); 394 } 395 396 // Add/sub assign a number to a dimensionless quantity 397 /// ditto 398 void opOpAssign(string op, T)(T scalar) 399 if (isNumeric!T && (op == "+" || op == "-")) 400 { 401 ensureEmpty!dimensions; 402 mixin("_value " ~ op ~ "= scalar;"); 403 } 404 405 // Mul/div assign another dimensionless quantity to a dimensionsless quantity 406 /// ditto 407 void opOpAssign(string op, Q)(auto ref const Q qty) 408 if (isQuantity!Q && (op == "*" || op == "/" || op == "%")) 409 { 410 ensureEmpty!dimensions; 411 mixin("_value" ~ op ~ "= qty._value;"); 412 } 413 414 // Mul/div assign a number to a quantity 415 /// ditto 416 void opOpAssign(string op, T)(T scalar) 417 if (isNumeric!T && (op == "*" || op == "/")) 418 { 419 mixin("_value" ~ op ~ "= scalar;"); 420 } 421 422 /// ditto 423 void opOpAssign(string op, T)(T scalar) 424 if (isNumeric!T && op == "%") 425 { 426 ensureEmpty!dimensions; 427 mixin("_value" ~ op ~ "= scalar;"); 428 } 429 430 // Exact equality between quantities 431 /// ditto 432 bool opEquals(Q)(auto ref const Q qty) const 433 if (isQuantity!Q) 434 { 435 ensureSameDim!(Q.dimensions); 436 return _value == qty._value; 437 } 438 439 // Exact equality between a dimensionless quantity and a number 440 /// ditto 441 bool opEquals(T)(T scalar) const 442 if (isNumeric!T) 443 { 444 ensureEmpty!dimensions; 445 return _value == scalar; 446 } 447 448 // Comparison between two quantities 449 /// ditto 450 int opCmp(Q)(auto ref const Q qty) const 451 if (isQuantity!Q) 452 { 453 ensureSameDim!(Q.dimensions); 454 if (_value == qty._value) 455 return 0; 456 if (_value < qty._value) 457 return -1; 458 return 1; 459 } 460 461 // Comparison between a dimensionless quantity and a number 462 /// ditto 463 int opCmp(T)(T scalar) const 464 if (isNumeric!T) 465 { 466 ensureEmpty!dimensions; 467 if (_value < scalar) 468 return -1; 469 if (_value > scalar) 470 return 1; 471 return 0; 472 } 473 474 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 475 { 476 sink.formatValue(_value, fmt); 477 sink(" "); 478 sink.formattedWrite!"%s"(dimensions); 479 } 480 } 481 482 /++ 483 Creates a new monodimensional unit as a Quantity. 484 485 Params: 486 N = The numeric type of the value part of the quantity. 487 488 dimSymbol = The symbol of the dimension of this quantity. 489 490 rank = The rank of the dimensions of this quantity in the dimension vector, 491 when combining this quantity with other oned. 492 +/ 493 auto unit(N, string dimSymbol, size_t rank = size_t.max)() 494 { 495 enum dims = Dimensions.mono(dimSymbol, rank); 496 return Quantity!(N, dims).make(1); 497 } 498 /// 499 unittest 500 { 501 enum meter = unit!(double, "L", 1); 502 enum kilogram = unit!(double, "M", 2); 503 // Dimensions will be in this order: L M 504 } 505 506 /// Tests whether T is a quantity type. 507 template isQuantity(T) 508 { 509 import std.traits : Unqual; 510 511 alias U = Unqual!T; 512 static if (is(U == Quantity!X, X...)) 513 enum isQuantity = true; 514 else 515 enum isQuantity = false; 516 } 517 518 /// Basic math functions that work with Quantity. 519 auto square(Q)(auto ref const Q quantity) 520 if (isQuantity!Q) 521 { 522 return Quantity!(Q.valueType, Q.dimensions.pow(2)).make(quantity._value ^^ 2); 523 } 524 525 /// ditto 526 auto sqrt(Q)(auto ref const Q quantity) 527 if (isQuantity!Q) 528 { 529 return Quantity!(Q.valueType, Q.dimensions.powinverse(2)).make(std.math.sqrt(quantity._value)); 530 } 531 532 /// ditto 533 auto cubic(Q)(auto ref const Q quantity) 534 if (isQuantity!Q) 535 { 536 return Quantity!(Q.valueType, Q.dimensions.pow(3)).make(quantity._value ^^ 3); 537 } 538 539 /// ditto 540 auto cbrt(Q)(auto ref const Q quantity) 541 if (isQuantity!Q) 542 { 543 return Quantity!(Q.valueType, Q.dimensions.powinverse(3)).make(std.math.cbrt(quantity._value)); 544 } 545 546 /// ditto 547 auto pow(int n, Q)(auto ref const Q quantity) 548 if (isQuantity!Q) 549 { 550 return Quantity!(Q.valueType, Q.dimensions.pow(n)).make(std.math.pow(quantity._value, n)); 551 } 552 553 /// ditto 554 auto nthRoot(int n, Q)(auto ref const Q quantity) 555 if (isQuantity!Q) 556 { 557 return Quantity!(Q.valueType, Q.dimensions.powinverse(n)).make( 558 std.math.pow(quantity._value, 1.0 / n)); 559 } 560 561 /// ditto 562 Q abs(Q)(auto ref const Q quantity) 563 if (isQuantity!Q) 564 { 565 return Q.make(std.math.fabs(quantity._value)); 566 }