1 /++ 2 This module defines dimensionally variant quantities, for use mainly at run time. 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.runtime.qvariant; 10 11 import quantities.compiletime.quantity : isQuantity; 12 import quantities.internal.dimensions; 13 14 import std.conv; 15 import std.exception; 16 import std.format; 17 import std.math; 18 import std.string; 19 import std.traits; 20 21 /++ 22 Exception thrown when operating on two units that are not interconvertible. 23 +/ 24 class DimensionException : Exception 25 { 26 /// Holds the dimensions of the quantity currently operated on 27 Dimensions thisDim; 28 /// Holds the dimensions of the eventual other operand 29 Dimensions otherDim; 30 31 mixin basicExceptionCtors; 32 33 this(string msg, Dimensions thisDim, Dimensions otherDim, 34 string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow 35 { 36 super(msg, file, line, next); 37 this.thisDim = thisDim; 38 this.otherDim = otherDim; 39 } 40 } 41 /// 42 unittest 43 { 44 import std.exception : assertThrown; 45 46 enum meter = unit!double("L"); 47 enum second = unit!double("T"); 48 49 } 50 51 /++ 52 A dimensionnaly variant quantity. 53 54 Params: 55 N = the numeric type of the quantity. 56 57 See_Also: 58 QVariant has the same public members and overloaded operators as Quantity. 59 +/ 60 struct QVariant(N) 61 { 62 static assert(isNumeric!N, "Incompatible type: " ~ N.stringof); 63 64 private: 65 N _value; 66 Dimensions _dimensions; 67 68 void checkDim(Dimensions dim) @safe pure const 69 { 70 enforce(_dimensions == dim, 71 new DimensionException("Incompatible dimensions", _dimensions, dim)); 72 } 73 74 void checkDimensionless() @safe pure const 75 { 76 enforce(_dimensions.empty, new DimensionException("Not dimensionless", 77 _dimensions, Dimensions.init)); 78 } 79 80 package(quantities): 81 alias valueType = N; 82 83 N rawValue() const 84 { 85 return _value; 86 } 87 88 public: 89 // Creates a new quantity with non-empty dimensions 90 this(T)(T scalar, const Dimensions dim) 91 if (isNumeric!T) 92 { 93 _value = scalar; 94 _dimensions = dim; 95 } 96 97 /// Creates a new quantity from another one with the same dimensions 98 this(Q)(auto ref const Q qty) 99 if (isQVariant!Q) 100 { 101 _value = qty._value; 102 _dimensions = qty._dimensions; 103 } 104 105 /// Ditto 106 this(Q)(auto ref const Q qty) 107 if (isQuantity!Q) 108 { 109 import quantities.compiletime.quantity : qVariant; 110 111 this = qty.qVariant; 112 } 113 114 /// Creates a new dimensionless quantity from a number 115 this(T)(T scalar) 116 if (isNumeric!T) 117 { 118 _dimensions = Dimensions.init; 119 _value = scalar; 120 } 121 122 /// Returns the dimensions of the quantity 123 Dimensions dimensions() @property const 124 { 125 return _dimensions; 126 } 127 128 /++ 129 Implicitly convert a dimensionless value to the value type. 130 131 Calling get will throw DimensionException if the quantity is not 132 dimensionless. 133 +/ 134 N get() const 135 { 136 checkDimensionless; 137 return _value; 138 } 139 140 alias get this; 141 142 /++ 143 Gets the _value of this quantity when expressed in the given target unit. 144 +/ 145 N value(Q)(auto ref const Q target) const 146 if (isQVariantOrQuantity!Q) 147 { 148 checkDim(target.dimensions); 149 return _value / target.rawValue; 150 } 151 /// 152 @safe pure unittest 153 { 154 auto minute = unit!int("T"); 155 auto hour = 60 * minute; 156 157 QVariant!int time = 120 * minute; 158 assert(time.value(hour) == 2); 159 assert(time.value(minute) == 120); 160 } 161 162 /++ 163 Test whether this quantity is dimensionless 164 +/ 165 bool isDimensionless() @property const 166 { 167 return _dimensions.empty; 168 } 169 170 /++ 171 Tests wheter this quantity has the same dimensions as another one. 172 +/ 173 bool isConsistentWith(Q)(auto ref const Q qty) const 174 if (isQVariantOrQuantity!Q) 175 { 176 return _dimensions == qty.dimensions; 177 } 178 /// 179 @safe pure unittest 180 { 181 auto second = unit!double("T"); 182 auto minute = 60 * second; 183 auto meter = unit!double("L"); 184 185 assert(minute.isConsistentWith(second)); 186 assert(!meter.isConsistentWith(second)); 187 } 188 189 /++ 190 Cast a dimensionless quantity to a numeric type. 191 192 The cast operation will throw DimensionException if the quantity is not 193 dimensionless. 194 +/ 195 T opCast(T)() const 196 if (isNumeric!T) 197 { 198 checkDimensionless; 199 return _value; 200 } 201 202 // Assign from another quantity 203 /// Operator overloading 204 ref QVariant opAssign(Q)(auto ref const Q qty) 205 if (isQVariantOrQuantity!Q) 206 { 207 _dimensions = qty.dimensions; 208 _value = qty.rawValue; 209 return this; 210 } 211 212 // Assign from a numeric value if this quantity is dimensionless 213 /// ditto 214 ref QVariant opAssign(T)(T scalar) 215 if (isNumeric!T) 216 { 217 _dimensions = Dimensions.init; 218 _value = scalar; 219 return this; 220 } 221 222 // Unary + and - 223 /// ditto 224 QVariant!N opUnary(string op)() const 225 if (op == "+" || op == "-") 226 { 227 return QVariant(mixin(op ~ "_value"), _dimensions); 228 } 229 230 // Unary ++ and -- 231 /// ditto 232 QVariant!N opUnary(string op)() 233 if (op == "++" || op == "--") 234 { 235 mixin(op ~ "_value;"); 236 return this; 237 } 238 239 // Add (or substract) two quantities if they share the same dimensions 240 /// ditto 241 QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 242 if (isQVariantOrQuantity!Q && (op == "+" || op == "-")) 243 { 244 checkDim(qty.dimensions); 245 return QVariant(mixin("_value" ~ op ~ "qty.rawValue"), _dimensions); 246 } 247 248 /// ditto 249 QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 250 if (isQVariantOrQuantity!Q && (op == "+" || op == "-")) 251 { 252 checkDim(qty.dimensions); 253 return QVariant(mixin("qty.rawValue" ~ op ~ "_value"), _dimensions); 254 } 255 256 // Add (or substract) a dimensionless quantity and a number 257 /// ditto 258 QVariant!N opBinary(string op, T)(T scalar) const 259 if (isNumeric!T && (op == "+" || op == "-")) 260 { 261 checkDimensionless; 262 return QVariant(mixin("_value" ~ op ~ "scalar"), _dimensions); 263 } 264 265 /// ditto 266 QVariant!N opBinaryRight(string op, T)(T scalar) const 267 if (isNumeric!T && (op == "+" || op == "-")) 268 { 269 checkDimensionless; 270 return QVariant(mixin("scalar" ~ op ~ "_value"), _dimensions); 271 } 272 273 // Multiply or divide a quantity by a number 274 /// ditto 275 QVariant!N opBinary(string op, T)(T scalar) const 276 if (isNumeric!T && (op == "*" || op == "/" || op == "%")) 277 { 278 return QVariant(mixin("_value" ~ op ~ "scalar"), _dimensions); 279 } 280 281 /// ditto 282 QVariant!N opBinaryRight(string op, T)(T scalar) const 283 if (isNumeric!T && op == "*") 284 { 285 return QVariant(mixin("scalar" ~ op ~ "_value"), _dimensions); 286 } 287 288 /// ditto 289 QVariant!N opBinaryRight(string op, T)(T scalar) const 290 if (isNumeric!T && (op == "/" || op == "%")) 291 { 292 return QVariant(mixin("scalar" ~ op ~ "_value"), ~_dimensions); 293 } 294 295 // Multiply or divide two quantities 296 /// ditto 297 QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 298 if (isQVariantOrQuantity!Q && (op == "*" || op == "/")) 299 { 300 return QVariant(mixin("(_value" ~ op ~ "qty.rawValue)"), 301 mixin("_dimensions" ~ op ~ "qty.dimensions")); 302 } 303 304 /// ditto 305 QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 306 if (isQVariantOrQuantity!Q && (op == "*" || op == "/")) 307 { 308 return QVariant(mixin("(qty.rawValue" ~ op ~ "_value)"), 309 mixin("qty.dimensions" ~ op ~ "_dimensions")); 310 } 311 312 /// ditto 313 QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 314 if (isQVariantOrQuantity!Q && (op == "%")) 315 { 316 checkDim(qty.dimensions); 317 return QVariant(_value % qty.rawValue, _dimensions); 318 } 319 320 /// ditto 321 QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 322 if (isQVariantOrQuantity!Q && (op == "%")) 323 { 324 checkDim(qty.dimensions); 325 return QVariant(qty.rawValue % _value, _dimensions); 326 } 327 328 /// ditto 329 QVariant!N opBinary(string op, T)(T power) const 330 if (isIntegral!T && op == "^^") 331 { 332 return QVariant(_value ^^ power, _dimensions.pow(Rational(power))); 333 } 334 335 /// ditto 336 QVariant!N opBinary(string op)(Rational power) const 337 if (op == "^^") 338 { 339 return QVariant(std.math.pow(_value, cast(N) power), _dimensions.pow(power)); 340 } 341 342 // Add/sub assign with a quantity that shares the same dimensions 343 /// ditto 344 void opOpAssign(string op, Q)(auto ref const Q qty) 345 if (isQVariantOrQuantity!Q && (op == "+" || op == "-")) 346 { 347 checkDim(qty.dimensions); 348 mixin("_value " ~ op ~ "= qty.rawValue;"); 349 } 350 351 // Add/sub assign a number to a dimensionless quantity 352 /// ditto 353 void opOpAssign(string op, T)(T scalar) 354 if (isNumeric!T && (op == "+" || op == "-")) 355 { 356 checkDimensionless; 357 mixin("_value " ~ op ~ "= scalar;"); 358 } 359 360 // Mul/div assign another quantity to a quantity 361 /// ditto 362 void opOpAssign(string op, Q)(auto ref const Q qty) 363 if (isQVariantOrQuantity!Q && (op == "*" || op == "/" || op == "%")) 364 { 365 mixin("_value" ~ op ~ "= qty.rawValue;"); 366 static if (op == "*") 367 _dimensions = _dimensions * qty.dimensions; 368 else 369 _dimensions = _dimensions / qty.dimensions; 370 } 371 372 // Mul/div assign a number to a quantity 373 /// ditto 374 void opOpAssign(string op, T)(T scalar) 375 if (isNumeric!T && (op == "*" || op == "/")) 376 { 377 mixin("_value" ~ op ~ "= scalar;"); 378 } 379 380 /// ditto 381 void opOpAssign(string op, T)(T scalar) 382 if (isNumeric!T && op == "%") 383 { 384 checkDimensionless; 385 mixin("_value" ~ op ~ "= scalar;"); 386 } 387 388 // Exact equality between quantities 389 /// ditto 390 bool opEquals(Q)(auto ref const Q qty) const 391 if (isQVariantOrQuantity!Q) 392 { 393 checkDim(qty.dimensions); 394 return _value == qty.rawValue; 395 } 396 397 // Exact equality between a dimensionless quantity and a number 398 /// ditto 399 bool opEquals(T)(T scalar) const 400 if (isNumeric!T) 401 { 402 checkDimensionless; 403 return _value == scalar; 404 } 405 406 // Comparison between two quantities 407 /// ditto 408 int opCmp(Q)(auto ref const Q qty) const 409 if (isQVariantOrQuantity!Q) 410 { 411 checkDim(qty.dimensions); 412 if (_value == qty.rawValue) 413 return 0; 414 if (_value < qty.rawValue) 415 return -1; 416 return 1; 417 } 418 419 // Comparison between a dimensionless quantity and a number 420 /// ditto 421 int opCmp(T)(T scalar) const 422 if (isNumeric!T) 423 { 424 checkDimensionless; 425 if (_value < scalar) 426 return -1; 427 if (_value > scalar) 428 return 1; 429 return 0; 430 } 431 432 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 433 { 434 sink.formatValue(_value, fmt); 435 sink(" "); 436 sink.formattedWrite!"%s"(_dimensions); 437 } 438 } 439 440 /// Creates a new monodimensional unit as a QVariant 441 QVariant!N unit(N)(string symbol) 442 { 443 return QVariant!N(N(1), Dimensions.mono(symbol)); 444 } 445 446 // Tests whether T is a quantity type 447 template isQVariant(T) 448 { 449 alias U = Unqual!T; 450 static if (is(U == QVariant!X, X...)) 451 enum isQVariant = true; 452 else 453 enum isQVariant = false; 454 } 455 456 enum isQVariantOrQuantity(T) = isQVariant!T || isQuantity!T; 457 458 /// Turns a Quantity into a QVariant 459 auto qVariant(Q)(auto ref const Q qty) 460 if (isQuantity!Q) 461 { 462 return QVariant!(Q.valueType)(qty.rawValue, qty.dimensions); 463 } 464 465 /// Turns a scalar into a dimensionless QVariant 466 auto qVariant(N)(N scalar) 467 if (isNumeric!N) 468 { 469 return QVariant!N(scalar, Dimensions.init); 470 } 471 472 /++ 473 Creates a new prefix function that multiplies a QVariant by a _factor. 474 +/ 475 template prefix(alias factor) 476 { 477 alias N = typeof(factor); 478 static assert(isNumeric!N, "Incompatible type: " ~ N.stringof); 479 480 auto prefix(Q)(auto ref const Q base) 481 if (isQVariantOrQuantity!Q) 482 { 483 return base * factor; 484 } 485 } 486 /// 487 @safe pure unittest 488 { 489 import std.math : approxEqual; 490 491 auto meter = unit!double("L"); 492 alias milli = prefix!1e-3; 493 assert(milli(meter).value(meter).approxEqual(1e-3)); 494 } 495 496 /// Basic math functions that work with QVariant. 497 auto square(Q)(auto ref const Q quantity) 498 if (isQVariant!Q) 499 { 500 return Q(quantity._value ^^ 2, quantity._dimensions.pow(Rational(2))); 501 } 502 503 /// ditto 504 auto sqrt(Q)(auto ref const Q quantity) 505 if (isQVariant!Q) 506 { 507 return Q(std.math.sqrt(quantity._value), quantity._dimensions.powinverse(Rational(2))); 508 } 509 510 /// ditto 511 auto cubic(Q)(auto ref const Q quantity) 512 if (isQVariant!Q) 513 { 514 return Q(quantity._value ^^ 3, quantity._dimensions.pow(Rational(3))); 515 } 516 517 /// ditto 518 auto cbrt(Q)(auto ref const Q quantity) 519 if (isQVariant!Q) 520 { 521 return Q(std.math.cbrt(quantity._value), quantity._dimensions.powinverse(Rational(3))); 522 } 523 524 /// ditto 525 auto pow(int n, Q)(auto ref const Q quantity) 526 if (isQVariant!Q) 527 { 528 return Q(std.math.pow(quantity._value, n), quantity._dimensions.pow(Rational(n))); 529 } 530 531 /// ditto 532 auto nthRoot(int n, Q)(auto ref const Q quantity) 533 if (isQVariant!Q) 534 { 535 return Q(std.math.pow(quantity._value, 1.0 / n), quantity._dimensions.powinverse(Rational(n))); 536 } 537 538 /// ditto 539 Q abs(Q)(auto ref const Q quantity) 540 if (isQVariant!Q) 541 { 542 return Q(std.math.fabs(quantity._value), quantity._dimensions); 543 }