1 /++ 2 This module defines dimensionnaly variant quantities. 3 4 Copyright: Copyright 2013-2015, 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.qvariant; 10 11 import quantities.internal.dimensions; 12 import quantities.base; 13 import quantities.parsing : DimensionException; 14 import std.exception; 15 import std.format; 16 import std.string; 17 import std.traits; 18 19 version (unittest) import std.math : approxEqual; 20 21 /++ 22 QVariant is analog to Quantity except that the dimensions are stored in a 23 private field instead of a type parameter. This makes QVariant "dimensionnaly 24 variant", so that a variable of type QVariant can hold quantities of variable 25 dimensions. Yet, only operations that are dimensionnaly consistent can be 26 performed on QVariant variables. 27 28 Warning: Contrary to Quantity, where all dimensional operations are done at 29 compile-time, the dimensions of QVariant ate computed at runtime for each 30 operation. This has a significant performance cost. Only use QVariant in 31 situation where using Quantity is not possible, that is when the dimensions of 32 the quantities are only known at runtime. 33 34 Params: 35 N = the numeric type of the quantity. 36 37 See_Also: 38 QVariant has the same public members and overloaded operators as Quantity. 39 +/ 40 struct QVariant(N) 41 { 42 static assert(isNumeric!N, "Incompatible type: " ~ N.stringof); 43 44 private: 45 void checkDim(in Dimensions dim) const pure @safe 46 { 47 enforceEx!DimensionException(dim == dimensions, 48 "Dimension error: %s is not compatible with %s" 49 .format(dim.toString, dimensions.toString)); 50 } 51 52 static void checkValueType(T)() 53 { 54 static assert(is(T : valueType), "%s is not implicitly convertible to %s" 55 .format(T.stringof, valueType.stringof)); 56 } 57 58 package: 59 N _value; 60 Dimensions dimensions; 61 62 // Should be a constructor 63 // Workaround for @@BUG 5770@@ 64 // (https://d.puremagic.com/issues/show_bug.cgi?id=5770) 65 // "Template constructor bypass access check" 66 static QVariant make(T)(T value, in Dimensions dim) @trusted 67 if (isNumeric!T) 68 { 69 checkValueType!T; 70 QVariant result = void; 71 result._value = value; 72 // The cast if safe: dim is a unique duplicate 73 result.dimensions = cast(Dimensions) dim; 74 return result; 75 } 76 77 // Gets the internal number of this quantity. 78 N rawValue() const pure nothrow @nogc @safe 79 { 80 return _value; 81 } 82 83 public: 84 alias valueType = N; 85 86 // Gets the base unit of this quantity. 87 QVariant baseUnit() pure @safe 88 { 89 N one = 1; 90 return QVariant.make(one, dimensions); 91 } 92 93 // Creates a new quantity from another one with the same dimensions 94 this(Q)(Q other) 95 if (isQVariant!Q || isQuantity!Q) 96 { 97 checkValueType!(Q.valueType); 98 _value = other.rawValue; 99 dimensions = other.dimensions; 100 } 101 102 // Creates a new dimensionless quantity from a number 103 this(T)(T value) 104 if (isNumeric!T) 105 { 106 checkValueType!T; 107 dimensions = Dimensions.init; 108 _value = value; 109 } 110 111 // Implicitly convert a dimensionless value to the value type 112 N get() const pure @safe 113 { 114 checkDim(Dimensions.init); 115 return _value; 116 } 117 alias get this; 118 119 /+ 120 Gets the _value of this quantity expressed in the given target unit. 121 +/ 122 N value(Q)(Q target) const 123 if (isQVariant!Q || isQuantity!Q) 124 { 125 checkDim(target.dimensions); 126 checkValueType!(Q.valueType); 127 return _value / target.rawValue; 128 } 129 // 130 pure @safe unittest 131 { 132 import quantities.si : minute, hour; 133 134 QVariant!double time = 120 * minute; 135 assert(time.value(hour) == 2); 136 assert(time.value(minute) == 120); 137 } 138 139 /+ 140 Tests wheter this quantity has the same dimensions as another one. 141 +/ 142 bool isConsistentWith(Q)(Q other) const 143 if (isQVariant!Q || isQuantity!Q) 144 { 145 return dimensions == other.dimensions; 146 } 147 // 148 pure @safe unittest 149 { 150 import quantities.si : minute, second, meter; 151 152 assert(minute.qVariant.isConsistentWith(second)); 153 assert(!meter.qVariant.isConsistentWith(second)); 154 } 155 156 // Cast a QVariant to an equivalent Quantity 157 Q opCast(Q)() const 158 if (isQuantity!Q) 159 { 160 checkDim(Q.dimensions); 161 checkValueType!(Q.valueType); 162 return Q.make(_value); 163 } 164 165 // Cast a dimensionless quantity to a numeric type 166 T opCast(T)() const 167 if (isNumeric!T) 168 { 169 checkDim(Dimensions.init); 170 checkValueType!T; 171 return _value; 172 } 173 174 // Overloaded operators. 175 // Only dimensionally correct operations will compile. 176 177 // Assign from another quantity 178 void opAssign(Q)(Q other) 179 if (isQVariant!Q || isQuantity!Q) 180 { 181 checkValueType!(Q.valueType); 182 dimensions = other.dimensions; 183 _value = other.rawValue; 184 } 185 186 // Assign from a numeric value if this quantity is dimensionless 187 // ditto 188 void opAssign(T)(T other) 189 if (isNumeric!T) 190 { 191 checkValueType!T; 192 dimensions = Dimensions.init; 193 _value = other; 194 } 195 196 // Unary + and - 197 // ditto 198 auto opUnary(string op)() const 199 if (op == "+" || op == "-") 200 { 201 return QVariant.make(mixin(op ~ "_value"), dimensions); 202 } 203 204 // Unary ++ and -- 205 // ditto 206 auto opUnary(string op)() 207 if (op == "++" || op == "--") 208 { 209 mixin(op ~ "_value;"); 210 return this; 211 } 212 213 // Add (or substract) two quantities if they share the same dimensions 214 // ditto 215 auto opBinary(string op, Q)(Q other) const 216 if ((isQVariant!Q || isQuantity!Q) && (op == "+" || op == "-")) 217 { 218 checkDim(other.dimensions); 219 checkValueType!(Q.valueType); 220 return QVariant.make(mixin("_value" ~ op ~ "other.rawValue"), dimensions); 221 } 222 223 // ditto 224 auto opBinaryRight(string op, Q)(Q other) const 225 if ((isQVariant!Q || isQuantity!Q) && (op == "+" || op == "-")) 226 { 227 return opBinary!op(other); 228 } 229 230 // Add (or substract) a dimensionless quantity and a number 231 // ditto 232 auto opBinary(string op, T)(T other) const 233 if (isNumeric!T && (op == "+" || op == "-")) 234 { 235 checkDim(Dimensions.init); 236 checkValueType!T; 237 return QVariant.make(mixin("_value" ~ op ~ "other"), dimensions); 238 } 239 240 // ditto 241 auto opBinaryRight(string op, T)(T other) const 242 if (isNumeric!T && (op == "+" || op == "-")) 243 { 244 return opBinary!op(other); 245 } 246 247 // Multiply or divide two quantities 248 // ditto 249 auto opBinary(string op, Q)(Q other) const 250 if ((isQVariant!Q || isQuantity!Q) && (op == "*" || op == "/" || op == "%")) 251 { 252 checkValueType!(Q.valueType); 253 return QVariant.make(mixin("(_value" ~ op ~ "other.rawValue)"), 254 dimensions.binop!op(other.dimensions)); 255 } 256 257 // ditto 258 auto opBinaryRight(string op, Q)(Q other) const 259 if ((isQVariant!Q || isQuantity!Q) && (op == "*" || op == "/" || op == "%")) 260 { 261 return this * other; 262 } 263 264 // Multiply or divide a quantity by a number 265 // ditto 266 auto opBinary(string op, T)(T other) const 267 if (isNumeric!T && (op == "*" || op == "/" || op == "%")) 268 { 269 checkValueType!T; 270 return QVariant.make(mixin("_value" ~ op ~ "other"), dimensions); 271 } 272 273 // ditto 274 auto opBinaryRight(string op, T)(T other) const 275 if (isNumeric!T && op == "*") 276 { 277 checkValueType!T; 278 return this * other; 279 } 280 281 // ditto 282 auto opBinaryRight(string op, T)(T other) const 283 if (isNumeric!T && (op == "/" || op == "%")) 284 { 285 checkValueType!T; 286 return QVariant.make(mixin("other" ~ op ~ "_value"), dimensions.invert()); 287 } 288 289 // ditto 290 auto opBinary(string op, T)(T power) const 291 if (op == "^^") 292 { 293 if (__ctfe) 294 assert(false, "QVariant operator ^^ is not supported at compile-time"); 295 296 checkValueType!T; 297 return QVariant.make(_value^^power, dimensions.pow(power)); 298 } 299 300 // Add/sub assign with a quantity that shares the same dimensions 301 // ditto 302 void opOpAssign(string op, Q)(Q other) 303 if ((isQVariant!Q || isQuantity!Q) && (op == "+" || op == "-")) 304 { 305 checkDim(other.dimensions); 306 checkValueType!(Q.valueType); 307 mixin("_value " ~ op ~ "= other.rawValue;"); 308 } 309 310 // Add/sub assign a number to a dimensionless quantity 311 // ditto 312 void opOpAssign(string op, T)(T other) 313 if (isNumeric!T && (op == "+" || op == "-")) 314 { 315 checkDim(Dimensions.init); 316 checkValueType!T; 317 mixin("_value " ~ op ~ "= other;"); 318 } 319 320 // Mul/div assign with a dimensionless quantity 321 // ditto 322 void opOpAssign(string op, Q)(Q other) 323 if ((isQVariant!Q || isQuantity!Q) && (op == "*" || op == "/" || op == "%")) 324 { 325 checkValueType!(Q.valueType); 326 mixin("_value" ~ op ~ "= other.rawValue;"); 327 dimensions = dimensions.binop!op(other.dimensions); 328 } 329 330 // Mul/div assign with a number 331 // ditto 332 void opOpAssign(string op, T)(T other) 333 if (isNumeric!T && (op == "*" || op == "/" || op == "%")) 334 { 335 checkValueType!T; 336 mixin("_value" ~ op ~ "= other;"); 337 } 338 339 // Exact equality between quantities 340 // ditto 341 bool opEquals(Q)(Q other) const 342 if (isQVariant!Q || isQuantity!Q) 343 { 344 checkDim(other.dimensions); 345 return _value == other.rawValue; 346 } 347 348 // Exact equality between a dimensionless quantity and a number 349 // ditto 350 bool opEquals(T)(T other) const 351 if (isNumeric!T) 352 { 353 checkDim(Dimensions.init); 354 return _value == other; 355 } 356 357 // Comparison between two quantities 358 // ditto 359 int opCmp(Q)(Q other) const 360 if (isQVariant!Q || isQuantity!Q) 361 { 362 checkDim(other.dimensions); 363 if (_value == other.rawValue) 364 return 0; 365 if (_value < other.rawValue) 366 return -1; 367 return 1; 368 } 369 370 // Comparison between a dimensionless quantity and a number 371 // ditto 372 int opCmp(T)(T other) const 373 if (isNumeric!T) 374 { 375 checkDim(Dimensions.init); 376 if (_value < other) 377 return -1; 378 if (_value > other) 379 return 1; 380 return 0; 381 } 382 383 // String formatting function 384 void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 385 { 386 sink.formatValue(_value, fmt); 387 sink(" "); 388 sink(dimensions.toString); 389 } 390 } 391 392 /// Converts a Quantity to an equivalent QVariant 393 auto qVariant(Q)(Q quantity) 394 { 395 return QVariant!(Q.valueType).make(quantity.rawValue, quantity.dimensions); 396 } 397 /// 398 pure @safe unittest 399 { 400 import quantities.si : meter, second; 401 402 auto speed = 42 * meter/second; 403 auto qspeed = speed.qVariant; 404 assert(qspeed.value(meter/second) == 42); 405 } 406 407 // Tests whether T is a quantity type 408 template isQVariant(T) 409 { 410 alias U = Unqual!T; 411 static if (is(U == QVariant!X, X...)) 412 enum isQVariant = true; 413 else 414 enum isQVariant = false; 415 } 416 417 pure @safe unittest // QVariant constructor 418 { 419 import quantities.si : minute, second, radian; 420 421 QVariant!double time = typeof(second)(1 * minute); 422 assert(time.value(second) == 60); 423 assert(time.baseUnit == qVariant(second)); 424 425 QVariant!double angle = 2; 426 assert(angle.value(radian) == 2); 427 } 428 429 pure @safe unittest // QVariant.alias this 430 { 431 import quantities.si : radian; 432 433 static double foo(double d) { return d; } 434 assert(foo(2 * qVariant(radian)) == 2); 435 } 436 437 pure @safe unittest // QVariant.opCast 438 { 439 import quantities.si : meter, radian, Angle; 440 441 auto angle = cast(Angle) (12 * radian.qVariant); 442 assert(angle.value(radian) == 12); 443 444 QVariant!double angle2 = 12 * radian; 445 assert(cast(double) angle2 == 12); 446 } 447 448 pure @safe unittest // QVariant.opAssign Q = Q 449 { 450 import quantities.si : meter, second, radian; 451 452 QVariant!double var = meter; 453 var = 100 * meter; 454 assert(var.value(meter) == 100); 455 456 var /= 5 * second; 457 assert(var.value(meter/second).approxEqual(20)); 458 459 var = 3.14 * radian; 460 } 461 462 pure @safe unittest // QVariant.opAssign Q = N 463 { 464 import quantities.si : radian; 465 466 QVariant!double angle = radian; 467 angle = 2; 468 assert(angle.value(radian) == 2); 469 } 470 471 pure @safe unittest // QVariant.opUnary +Q -Q ++Q --Q 472 { 473 import quantities.si : meter; 474 475 QVariant!double length = + meter.qVariant; 476 assert(length == 1 * meter); 477 QVariant!double length2 = - meter.qVariant; 478 assert(length2 == -1 * meter); 479 480 QVariant!double len = ++meter; 481 assert(len.value(meter).approxEqual(2)); 482 len = --meter; 483 assert(len.value(meter).approxEqual(0)); 484 ++len; 485 assert(len.value(meter).approxEqual(1)); 486 len++; 487 assert(len.value(meter).approxEqual(2)); 488 } 489 490 pure @safe unittest // QVariant.opBinary Q*N Q/N 491 { 492 import quantities.si : second; 493 494 QVariant!double time = second * 60; 495 assert(time.value(second) == 60); 496 QVariant!double time2 = second / 2; 497 assert(time2.value(second) == 1.0/2); 498 } 499 500 pure @safe unittest // QVariant.opBinary Q+Q Q-Q 501 { 502 import quantities.si : meter; 503 504 QVariant!double length = meter + meter; 505 assert(length.value(meter) == 2); 506 QVariant!double length2 = length - meter; 507 assert(length2.value(meter) == 1); 508 } 509 510 pure @safe unittest // QVariant.opBinary Q+N Q-N 511 { 512 import quantities.si : radian; 513 514 QVariant!double angle = radian + 1; 515 assert(angle.value(radian) == 2); 516 QVariant!double angle2 = angle - 1; 517 assert(angle2.value(radian) == 1); 518 } 519 520 pure @safe unittest // QVariant.opBinary Q*Q Q/Q 521 { 522 import quantities.si : meter, minute, second; 523 524 QVariant!double hertz = 1 / second; 525 526 QVariant!double length = meter * 5; 527 QVariant!double surface = length * length; 528 assert(surface.value(meter * meter) == 5*5); 529 QVariant!double length2 = surface / length; 530 assert(length2.value(meter) == 5); 531 532 QVariant!double x = minute / second; 533 assert(x.rawValue == 60); 534 535 QVariant!double y = minute * hertz; 536 assert(y.rawValue == 60); 537 } 538 539 pure @safe unittest // QVariant.opBinaryRight N*Q 540 { 541 import quantities.si : meter; 542 543 QVariant!double length = 100 * meter; 544 assert(length == meter * 100); 545 } 546 547 pure @safe unittest // QVariant.opBinaryRight N/Q 548 { 549 import quantities.si : meter; 550 551 QVariant!double x = 1 / (2 * meter); 552 assert(x.value(1/meter) == 1.0/2); 553 } 554 555 pure @safe unittest // QVariant.opBinary Q%Q Q%N N%Q 556 { 557 import quantities.si : meter; 558 559 QVariant!double x = 258.1 * meter; 560 QVariant!double y1 = x % (50 * meter); 561 assert((cast(double) y1).approxEqual(8.1)); 562 QVariant!double y2 = x % 50; 563 assert(y2.value(meter).approxEqual(8.1)); 564 } 565 566 pure @safe unittest // QVariant.opBinary Q^^N 567 { 568 import quantities.si : meter; 569 import quantities.si : cubic; 570 571 QVariant!double x = 2 * meter; 572 assert((x^^3).value(cubic(meter)).approxEqual(8)); 573 } 574 575 pure @safe unittest // QVariant.opOpAssign Q+=Q Q-=Q 576 { 577 import quantities.si : second; 578 579 QVariant!double time = 10 * second; 580 time += 50 * second; 581 assert(time.value(second).approxEqual(60)); 582 time -= 40 * second; 583 assert(time.value(second).approxEqual(20)); 584 } 585 586 pure @safe unittest // QVariant.opOpAssign Q*=N Q/=N Q%=N 587 { 588 import quantities.si : second; 589 590 QVariant!double time = 20 * second; 591 time *= 2; 592 assert(time.value(second).approxEqual(40)); 593 time /= 4; 594 assert(time.value(second).approxEqual(10)); 595 time %= 3; 596 assert(time.value(second).approxEqual(1)); 597 } 598 599 pure @safe unittest // QVariant.opEquals 600 { 601 import quantities.si : meter, minute, second, radian; 602 603 assert(qVariant(1 * minute) == qVariant(60 * second)); 604 assert(qVariant((1 / second) * meter) == qVariant(meter / second)); 605 assert(radian.qVariant == 1); 606 } 607 608 pure @safe unittest // QVariant.opCmp 609 { 610 import quantities.si : minute, second; 611 612 QVariant!double hour = 60 * minute; 613 assert(second.qVariant < minute.qVariant); 614 assert(minute.qVariant <= minute.qVariant); 615 assert(hour > minute); 616 assert(hour >= hour); 617 } 618 619 pure @safe unittest // Quantity.opCmp 620 { 621 import quantities.si : radian; 622 623 QVariant!double angle = 2 * radian; 624 assert(angle < 4); 625 assert(angle <= 2); 626 assert(angle > 1); 627 assert(angle >= 2); 628 } 629 630 unittest // Quantity.toString 631 { 632 import quantities.si : meter; 633 import std.conv : text; 634 635 QVariant!double length = 12 * meter; 636 assert(length.text == "12 [L]", length.text); 637 } 638 639 pure @safe unittest // Exceptions for incompatible dimensions 640 { 641 import quantities.si : meter, second; 642 import std.exception; 643 644 QVariant!double m = meter; 645 assertThrown!DimensionException(m.value(second)); 646 assertThrown!DimensionException(m + second); 647 assertThrown!DimensionException(m - second); 648 assertThrown!DimensionException(m + 1); 649 assertThrown!DimensionException(m - 1); 650 assertThrown!DimensionException(1 + m); 651 assertThrown!DimensionException(1 - m); 652 assertThrown!DimensionException(m += second); 653 assertThrown!DimensionException(m -= second); 654 assertThrown!DimensionException(m += 1); 655 assertThrown!DimensionException(m -= 1); 656 assertThrown!DimensionException(m == 1); 657 assertThrown!DimensionException(m == second); 658 assertThrown!DimensionException(m < second); 659 assertThrown!DimensionException(m < 1); 660 } 661 662 pure @safe unittest // Compile-time 663 { 664 import quantities.si : meter, second, radian, cubic; 665 666 enum length = 100 * meter.qVariant; 667 enum time = 5 * second.qVariant; 668 enum speed = length / time; 669 enum val = speed.value(meter/second); 670 static assert(val.approxEqual(20)); 671 672 version (none) 673 { 674 enum volume = length^^3; 675 static assert(volume.value(cubic(meter)).approxEqual(1e6)); 676 } 677 }