1 /++ 2 This module quantities that are checked at compile-time for 3 dimensional consistency. 4 5 Copyright: Copyright 2013-2018, Nicolas Sicard 6 Authors: Nicolas Sicard 7 License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 8 Source: $(LINK https://github.com/biozic/quantities) 9 +/ 10 module quantities.compiletime.quantity; 11 12 import quantities.internal.dimensions; 13 import quantities.runtime.qvariant; 14 import std.format; 15 import std.math; 16 import std.traits : isNumeric, isIntegral; 17 18 /++ 19 A quantity checked at compile-time for dimensional consistency. 20 21 Params: 22 N = the numeric type of the quantity. 23 24 See_Also: 25 QVariant has the same public members and overloaded operators as Quantity. 26 +/ 27 struct Quantity(N, alias unitSpec) 28 { 29 static assert(isNumeric!N); 30 static assert(isQVariant!(typeof(unitSpec))); 31 static assert(Quantity.sizeof == N.sizeof); 32 33 private: 34 N _value; 35 alias unit = unitSpec; 36 37 // Creates a new quantity with non-empty dimensions 38 static Quantity make(T)(T scalar) 39 if (isNumeric!T) 40 { 41 Quantity result; 42 result._value = scalar; 43 return result; 44 } 45 46 mixin template checkDim(alias u) 47 { 48 static if (unit.dimensions != u.dimensions) 49 static assert(false, 50 "Dimension error: %s is not consistent with %s".format(unit.dimensions, 51 u.dimensions)); 52 } 53 54 mixin template checkDimensionless(alias u) 55 { 56 static assert(u.isDimensionless, "Dimension error: %s instead of no dimensions"); 57 } 58 59 package(quantities): 60 alias valueType = N; 61 62 N rawValue() const 63 { 64 return _value; 65 } 66 67 public: 68 /// Creates a new quantity from another one with the same dimensions 69 this(Q)(auto ref const Q qty) 70 if (isQuantity!Q) 71 { 72 mixin checkDim!(Q.unit); 73 _value = qty._value; 74 } 75 76 /// Ditto 77 this(Q)(auto ref const Q qty) 78 if (isQVariant!Q) 79 { 80 import std.exception; 81 82 enforce(unit.dimensions == qty.dimensions, 83 new DimensionException("Incompatible dimensions", unit.dimensions, qty.dimensions)); 84 _value = qty.rawValue; 85 } 86 87 /// Creates a new dimensionless quantity from a number 88 this(T)(T scalar) 89 if (isNumeric!T && unit.isDimensionless) 90 { 91 _value = scalar; 92 } 93 94 /// Returns the dimensions of the quantity 95 enum dimensions = unit.dimensions; 96 97 /++ 98 Implicitly convert a dimensionless value to the value type. 99 +/ 100 static if (unit.isDimensionless) 101 { 102 N get() const 103 { 104 return _value; 105 } 106 107 alias get this; 108 } 109 110 /++ 111 Gets the _value of this quantity when expressed in the given target unit. 112 +/ 113 N value(Q)(auto ref const Q target) const 114 if (isQuantity!Q) 115 { 116 mixin checkDim!(Q.unit); 117 return _value / target._value; 118 } 119 120 /++ 121 Test whether this quantity is dimensionless 122 +/ 123 enum bool isDimensionless = unit.isDimensionless; 124 125 /++ 126 Tests wheter this quantity has the same dimensions as another one. 127 +/ 128 bool isConsistentWith(Q)(auto ref const Q qty) const 129 if (isQuantity!Q) 130 { 131 enum yesOrNo = unit.isConsistentWith(Q.unit); 132 return yesOrNo; 133 } 134 135 /++ 136 Cast a dimensionless quantity to a numeric type. 137 138 The cast operation will throw DimensionException if the quantity is not 139 dimensionless. 140 +/ 141 static if (unit.isDimensionless) 142 { 143 T opCast(T)() const 144 if (isNumeric!T) 145 { 146 return _value; 147 } 148 } 149 150 // Assign from another quantity 151 /// Operator overloading 152 ref Quantity opAssign(Q)(auto ref const Q qty) 153 if (isQuantity!Q) 154 { 155 mixin checkDim!(Q.unit); 156 _value = qty._value; 157 return this; 158 } 159 160 // Assign from a numeric value if this quantity is dimensionless 161 /// ditto 162 ref Quantity opAssign(T)(T scalar) 163 if (isNumeric!T) 164 { 165 mixin checkDimensionless!unit; 166 _value = scalar; 167 return this; 168 } 169 170 // Unary + and - 171 /// ditto 172 Quantity opUnary(string op)() const 173 if (op == "+" || op == "-") 174 { 175 return Quantity.make(mixin(op ~ "_value")); 176 } 177 178 // Unary ++ and -- 179 /// ditto 180 Quantity opUnary(string op)() 181 if (op == "++" || op == "--") 182 { 183 mixin(op ~ "_value;"); 184 return this; 185 } 186 187 // Add (or substract) two quantities if they share the same dimensions 188 /// ditto 189 Quantity opBinary(string op, Q)(auto ref const Q qty) const 190 if (isQuantity!Q && (op == "+" || op == "-")) 191 { 192 mixin checkDim!(Q.unit); 193 return Quantity.make(mixin("_value" ~ op ~ "qty._value")); 194 } 195 196 // Add (or substract) a dimensionless quantity and a number 197 /// ditto 198 Quantity opBinary(string op, T)(T scalar) const 199 if (isNumeric!T && (op == "+" || op == "-")) 200 { 201 mixin checkDimensionless!unit; 202 return Quantity.make(mixin("_value" ~ op ~ "scalar")); 203 } 204 205 /// ditto 206 Quantity opBinaryRight(string op, T)(T scalar) const 207 if (isNumeric!T && (op == "+" || op == "-")) 208 { 209 mixin checkDimensionless!unit; 210 return Quantity.make(mixin("scalar" ~ op ~ "_value")); 211 } 212 213 // Multiply or divide a quantity by a number 214 /// ditto 215 Quantity opBinary(string op, T)(T scalar) const 216 if (isNumeric!T && (op == "*" || op == "/" || op == "%")) 217 { 218 return Quantity.make(mixin("_value" ~ op ~ "scalar")); 219 } 220 221 /// ditto 222 Quantity opBinaryRight(string op, T)(T scalar) const 223 if (isNumeric!T && op == "*") 224 { 225 return Quantity.make(mixin("scalar" ~ op ~ "_value")); 226 } 227 228 /// ditto 229 auto opBinaryRight(string op, T)(T scalar) const 230 if (isNumeric!T && (op == "/" || op == "%")) 231 { 232 alias RQ = Quantity!(N, 1 / unit); 233 return RQ.make(mixin("scalar" ~ op ~ "_value")); 234 } 235 236 // Multiply or divide two quantities 237 /// ditto 238 auto opBinary(string op, Q)(auto ref const Q qty) const 239 if (isQuantity!Q && (op == "*" || op == "/")) 240 { 241 alias RQ = Quantity!(N, mixin("unit" ~ op ~ "Q.unit")); 242 return RQ.make(mixin("(_value" ~ op ~ "qty._value)")); 243 } 244 245 /// ditto 246 Quantity opBinary(string op, Q)(auto ref const Q qty) const 247 if (isQuantity!Q && (op == "%")) 248 { 249 mixin checkDim!(Q.unit); 250 return Quantity.make(mixin("(_value" ~ op ~ "qty._value)")); 251 } 252 253 // Add/sub assign with a quantity that shares the same dimensions 254 /// ditto 255 void opOpAssign(string op, Q)(auto ref const Q qty) 256 if (isQuantity!Q && (op == "+" || op == "-")) 257 { 258 mixin checkDim!(Q.unit); 259 mixin("_value " ~ op ~ "= qty._value;"); 260 } 261 262 // Add/sub assign a number to a dimensionless quantity 263 /// ditto 264 void opOpAssign(string op, T)(T scalar) 265 if (isNumeric!T && (op == "+" || op == "-")) 266 { 267 mixin checkDimensionless!unit; 268 mixin("_value " ~ op ~ "= scalar;"); 269 } 270 271 // Mul/div assign another dimensionless quantity to a dimensionsless quantity 272 /// ditto 273 void opOpAssign(string op, Q)(auto ref const Q qty) 274 if (isQuantity!Q && (op == "*" || op == "/" || op == "%")) 275 { 276 mixin checkDimensionless!unit; 277 mixin("_value" ~ op ~ "= qty._value;"); 278 } 279 280 // Mul/div assign a number to a quantity 281 /// ditto 282 void opOpAssign(string op, T)(T scalar) 283 if (isNumeric!T && (op == "*" || op == "/")) 284 { 285 mixin("_value" ~ op ~ "= scalar;"); 286 } 287 288 /// ditto 289 void opOpAssign(string op, T)(T scalar) 290 if (isNumeric!T && op == "%") 291 { 292 mixin checkDimensionless!unit; 293 mixin("_value" ~ op ~ "= scalar;"); 294 } 295 296 // Exact equality between quantities 297 /// ditto 298 bool opEquals(Q)(auto ref const Q qty) const 299 if (isQuantity!Q) 300 { 301 mixin checkDim!(Q.unit); 302 return _value == qty._value; 303 } 304 305 // Exact equality between a dimensionless quantity and a number 306 /// ditto 307 bool opEquals(T)(T scalar) const 308 if (isNumeric!T) 309 { 310 mixin checkDimensionless!unit; 311 return _value == scalar; 312 } 313 314 // Comparison between two quantities 315 /// ditto 316 int opCmp(Q)(auto ref const Q qty) const 317 if (isQuantity!Q) 318 { 319 mixin checkDim!(Q.unit); 320 if (_value == qty._value) 321 return 0; 322 if (_value < qty._value) 323 return -1; 324 return 1; 325 } 326 327 // Comparison between a dimensionless quantity and a number 328 /// ditto 329 int opCmp(T)(T scalar) const 330 if (isNumeric!T) 331 { 332 mixin checkDimensionless!unit; 333 if (_value < scalar) 334 return -1; 335 if (_value > scalar) 336 return 1; 337 return 0; 338 } 339 340 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 341 { 342 sink.formatValue(_value, fmt); 343 sink(" "); 344 sink.formattedWrite!"%s"(unit.dimensions); 345 } 346 } 347 348 /// Creates a new monodimensional unit as a QVariant 349 auto unit(N, string symbol)() 350 { 351 import quantities.runtime.qvariant; 352 353 enum u = quantities.runtime.qvariant.unit!N(symbol); 354 return Quantity!(N, u).make(1); 355 } 356 357 /// Tests whether T is a quantity type 358 template isQuantity(T) 359 { 360 import std.traits : Unqual; 361 362 alias U = Unqual!T; 363 static if (is(U == Quantity!X, X...)) 364 enum isQuantity = true; 365 else 366 enum isQuantity = false; 367 } 368 369 /// Basic math functions that work with Quantity. 370 auto square(Q)(auto ref const Q quantity) 371 if (isQuantity!Q) 372 { 373 enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.pow(2)); }(); 374 return Quantity!(Q.valueType, u).make(quantity._value ^^ 2); 375 } 376 377 /// ditto 378 auto sqrt(Q)(auto ref const Q quantity) 379 if (isQuantity!Q) 380 { 381 enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.powinverse(2)); }(); 382 return Quantity!(Q.valueType, u).make(std.math.sqrt(quantity._value)); 383 } 384 385 /// ditto 386 auto cubic(Q)(auto ref const Q quantity) 387 if (isQuantity!Q) 388 { 389 enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.pow(3)); }(); 390 return Quantity!(Q.valueType, u).make(quantity._value ^^ 3); 391 } 392 393 /// ditto 394 auto cbrt(Q)(auto ref const Q quantity) 395 if (isQuantity!Q) 396 { 397 enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.powinverse(3)); }(); 398 return Quantity!(Q.valueType, u).make(std.math.cbrt(quantity._value)); 399 } 400 401 /// ditto 402 auto pow(int n, Q)(auto ref const Q quantity) 403 if (isQuantity!Q) 404 { 405 enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.pow(n)); }(); 406 return Quantity!(Q.valueType, u).make(std.math.pow(quantity._value, n)); 407 } 408 409 /// ditto 410 auto nthRoot(int n, Q)(auto ref const Q quantity) 411 if (isQuantity!Q) 412 { 413 enum u = { return QVariant!(Q.valueType)(1, Q.dimensions.powinverse(n)); }(); 414 return Quantity!(Q.valueType, u).make(std.math.pow(quantity._value, 1.0 / n)); 415 } 416 417 /// ditto 418 Q abs(Q)(auto ref const Q quantity) 419 if (isQuantity!Q) 420 { 421 return Q.make(std.math.fabs(quantity._value)); 422 }