├── .editorconfig ├── .gitignore ├── .travis.yml ├── README.md ├── dub.sdl ├── publish-docs.sh ├── source └── quantities │ ├── common.d │ ├── compiletime.d │ ├── internal │ ├── dimensions.d │ └── si.d │ ├── package.d │ ├── parsing.d │ ├── runtime.d │ └── si.d └── tests ├── fake_units_tests.d ├── more_tests.d ├── quantity_tests.d └── qvariant_tests.d /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | tab_width = 4 4 | indent_size = 4 5 | indent_style = space 6 | max_line_length = 100 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.d] 11 | dfmt_brace_style = allman 12 | dfmt_template_constraint_style = always_newline_indent 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | bin 3 | docs/ 4 | docs.json 5 | dub.selections.json 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | matrix: 4 | include: 5 | - d: dmd 6 | - d: ldc 7 | sudo: false 8 | script: 9 | - dub test 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About _quantities_ 2 | 3 | [![Build Status](https://travis-ci.org/biozic/quantities.svg?branch=master)](https://travis-ci.org/biozic/quantities) 4 | 5 | The purpose of this small library is to perform automatic compile-time or 6 | run-time dimensional checking when dealing with quantities and units. 7 | 8 | There is no actual distinction between units and quantities, so there are no 9 | distinct quantity and unit types. All operations are actually done on 10 | quantities. For example, `meter` is both the unit _meter_ and the quantity _1m_. 11 | New quantities can be derived from other ones using operators or dedicated 12 | functions. 13 | 14 | Quantities can be parsed from strings at run time and compile time. 15 | 16 | The main SI units and prefixes are predefined. Units with other dimensions can 17 | be defined by the user. With a bit of work, a whole system of new units could be 18 | defined to use for calculations and parsing. 19 | 20 | Copyright 2013-2018, Nicolas Sicard. 21 | 22 | License: Boost License 1.0. 23 | 24 | ### Online documentation 25 | 26 | Check online documentation [here](https://biozic.github.io/quantities/quantities.html). 27 | 28 | ### Design rationale 29 | 30 | #### Quantities at compile time 31 | 32 | 1. The library defines a `Quantity` type (a template) that represents a physical 33 | quantity, or any user-defined type of quantity. A quantity can be seen as the 34 | product of a scalar value and a vector of dimensions. The vector of 35 | dimensions is known at compile time and is part of the type. 36 | 37 | 2. A `Quantity` is a wrapper struct around a numeric value, where the only 38 | payload is this numeric value; no other data is stored. So the memory size of 39 | a quantity is the same as its underlying numeric type. With optimizations on, 40 | the compiler generates the same code as if normal numeric values were used. 41 | 42 | For the moment, only built-in numeric types are handled. But it should be 43 | possible to make it work with any "number-like" type. 44 | 45 | 3. Two quantities with the same dimensions share the same type (assuming the 46 | underlying numeric types are the same). Thus functions and types using 47 | quantities generally won't have to be templated if the dimensions of the 48 | quantities are known at compile time. 49 | 50 | 4. All operations on `Quantity` values are statically checked for dimensional 51 | consistency. If a constuction, an assignment, a calculation (using overloaded 52 | operators or special functions) or parsing from string at compile-time is not 53 | dimensionnaly consistent, there's a compilation error. Most notably, 54 | calculations involving plain built-in numeric types (`double`, `int`, etc.) 55 | only work with quantities with no dimensions. 56 | 57 | Some operations (construction, assignment, `value` function, parsing from a 58 | run-time string) can use a `QVariant` argument. In this case, the checks are 59 | done at run-time. 60 | 61 | #### Quantities at run time 62 | 63 | 1. Since it is not always possible, nor sometimes desirable, to know the 64 | dimensions of quantities at compile time, the library defines a `QVariant` 65 | type, for which the vector of dimensions is not part of the type, but stored 66 | as a member along the numeric value. 67 | 68 | 2. Calculations *and* dimensionnal checks are done at run time. Both `QVariant` 69 | and `Quantity` can be used in the same expressions to a certain extent. 70 | 71 | 3. All quantities stored as `QVariant` share the same type, event if the 72 | dimensions of the quantities are different. 73 | 74 | 4. Only calculations that break dimensional consitencies are checked an throw a 75 | `DimensionException`. A `QVariant` can be reassigned a new quantity with 76 | other dimensions. 77 | 78 | 79 | #### Consequences of the design 80 | 81 | 1. The main consequence of principles #3 is that all quantities sharing the same 82 | dimensions are internally expressed in the same unit, which is the base unit 83 | for this quantity. For instance, all lengths are stored as meters, which is 84 | the base unit of length. The quantity _3 km_ is stored as _3000 m_, 85 | _2 min_ is stored as _120 s_, etc. 86 | 87 | The drawback (possibly an important one) is that, when assigning a 88 | new value to a quantity, the binary representation is preserved only if the 89 | quantity is expressed in the base unit. 90 | 91 | 2. An indirect consequence is that there is no unit symbol stored with a 92 | quantity. The only relevant symbol would have been the one of the base unit, 93 | but it's rarely the best choice. 94 | 95 | But in practice, when formatting a quantity, the unit is usually chosen in 96 | advance. If not, no simple algorithm is capable of guessing the relevant unit. 97 | So I have decided that a quantity wouldn't format itself correctly. Instead, for 98 | now, the `toString` function prints the value and the dimensions vector. 99 | 100 | To print the units properly, the user can use the `siFormat` functions 101 | (obviously, they work only for SI units at the moment), or use the result of the 102 | `value` method. 103 | 104 | ### Examples 105 | 106 | #### Synopsis at compile-time 107 | 108 | ```d 109 | import quantities.compiletime; 110 | import quantities.si; 111 | 112 | // Define a quantity from SI units 113 | auto distance = 384_400 * kilo(meter); 114 | 115 | // Define a quantity from a string 116 | auto speed = si!"299_792_458 m/s"; 117 | // Define a type for a quantity 118 | alias Speed = typeof(speed); 119 | 120 | // Calculations on quantities 121 | auto calculateTime(Length d, Speed s) 122 | { 123 | return d / s; 124 | } 125 | Time time = calculateTime(distance, speed); 126 | 127 | // Dimensions are checked at compile time for consistency 128 | static assert(!__traits(compiles, distance + speed)); 129 | 130 | // Format a quantity with a format specification known at compile-time 131 | assert(siFormat!"%.3f s"(time) == "1.282 s"); 132 | ``` 133 | 134 | #### Synopsis at run-time 135 | 136 | ```d 137 | import quantities.runtime; 138 | import quantities.si; 139 | import std.exception : assertThrown; 140 | 141 | // Define a quantity from SI units (using the helper function `qVariant`) 142 | auto distance = qVariant(384_400 * kilo(meter)); 143 | 144 | // Define a quantity from a string 145 | auto speed = parseSI("299_792_458 m/s"); 146 | 147 | // Calculations on quantities (checked at compile time for consistency) 148 | QVariant!double calculateTime(QVariant!double d, QVariant!double s) 149 | { 150 | return d / s; 151 | } 152 | auto time = calculateTime(distance, speed); 153 | 154 | // Dimensions are checked at run time for consistency 155 | assertThrown!DimensionException(distance + speed); 156 | 157 | // Format a quantity with a format specification known at run-time 158 | assert(siFormat("%.3f s", time) == "1.282 s"); 159 | ``` 160 | 161 | See more complete examples [at run 162 | time](https://biozic.github.io/quantities/quantities/runtime.html) 163 | and [at compile 164 | time](https://biozic.github.io/quantities/quantities/compiletime.html). 165 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "quantities" 2 | description "A library to work with quantities and units" 3 | homepage "https://github.com/biozic/quantities" 4 | authors "Nicolas Sicard" 5 | copyright "Copyright © 2013-2018, Nicolas Sicard" 6 | license "BSL-1.0" 7 | targetType "library" 8 | targetPath "bin" 9 | configuration "library" { 10 | targetName "quantities" 11 | } 12 | configuration "unittest" { 13 | sourcePaths "source" "tests" 14 | importPaths "tests" 15 | } 16 | configuration "benchmark" { 17 | targetName "quantities-benchmark" 18 | targetType "executable" 19 | sourcePaths "bench" 20 | } 21 | -------------------------------------------------------------------------------- /publish-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf docs 4 | dub build -b ddox 5 | cd docs 6 | git init 7 | git add . 8 | git commit -m 'Deployed doc to Github Pages' 9 | git push --force --quiet "https://github.com/biozic/quantities" master:gh-pages 10 | -------------------------------------------------------------------------------- /source/quantities/common.d: -------------------------------------------------------------------------------- 1 | module quantities.common; 2 | 3 | import quantities.runtime : isQVariantOrQuantity, unit; 4 | 5 | /++ 6 | Creates a new prefix function that multiplies a QVariant by a factor. 7 | +/ 8 | template prefix(alias fact) 9 | { 10 | import std.traits : isNumeric; 11 | 12 | alias N = typeof(fact); 13 | static assert(isNumeric!N, "Incompatible type: " ~ N.stringof); 14 | 15 | /// The prefix factor 16 | enum factor = fact; 17 | 18 | /// The prefix function 19 | auto prefix(Q)(auto ref const Q base) 20 | if (isQVariantOrQuantity!Q) 21 | { 22 | return base * fact; 23 | } 24 | } 25 | /// 26 | @safe pure unittest 27 | { 28 | auto meter = unit!double("L"); 29 | alias milli = prefix!1e-3; 30 | assert(milli(meter) == 1e-3 * meter); 31 | } 32 | -------------------------------------------------------------------------------- /source/quantities/compiletime.d: -------------------------------------------------------------------------------- 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 | } 567 | -------------------------------------------------------------------------------- /source/quantities/internal/dimensions.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Structs used to define units: rational 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 == 1) 35 | return; 36 | if (den < 0) 37 | { 38 | num = -num; 39 | den = -den; 40 | } 41 | immutable g = gcd(num, den); 42 | num /= g; 43 | den /= g; 44 | } 45 | 46 | bool isNormalized() @safe pure nothrow const 47 | { 48 | return den >= 0 && gcd(num, den) == 1; 49 | } 50 | 51 | public: 52 | /++ 53 | Create a rational number. 54 | 55 | Params: 56 | num = The numerator 57 | den = The denominator 58 | +/ 59 | this(int num, int den = 1) @safe pure nothrow 60 | { 61 | assert(den != 0, "Denominator is zero"); 62 | this.num = num; 63 | this.den = den; 64 | normalize(); 65 | } 66 | 67 | bool isInt() @property @safe pure nothrow const 68 | { 69 | return den == 1; 70 | } 71 | 72 | Rational inverted() @property @safe pure nothrow const 73 | { 74 | Rational result; 75 | result.num = den; 76 | result.den = num; 77 | assert(isNormalized); 78 | return result; 79 | } 80 | 81 | void opOpAssign(string op)(Rational other) @safe pure nothrow 82 | if (op == "+" || op == "-" || op == "*" || op == "/") 83 | { 84 | mixin("this = this" ~ op ~ "other;"); 85 | assert(isNormalized); 86 | } 87 | 88 | void opOpAssign(string op)(int value) @safe pure nothrow 89 | if (op == "+" || op == "-" || op == "*" || op == "/") 90 | { 91 | mixin("this = this" ~ op ~ "value;"); 92 | assert(isNormalized); 93 | } 94 | 95 | Rational opUnary(string op)() @safe pure nothrow const 96 | if (op == "+" || op == "-") 97 | out (result) 98 | { 99 | assert(result.isNormalized); 100 | } 101 | body 102 | { 103 | return Rational(mixin(op ~ "num"), den); 104 | } 105 | 106 | Rational opBinary(string op)(Rational other) @safe pure nothrow const 107 | if (op == "+" || op == "-") 108 | { 109 | auto ret = Rational(mixin("num * other.den" ~ op ~ "other.num * den"), den * other.den); 110 | ret.normalize(); 111 | return ret; 112 | } 113 | 114 | Rational opBinary(string op)(Rational other) @safe pure nothrow const 115 | if (op == "*") 116 | { 117 | auto ret = Rational(num * other.num, den * other.den); 118 | ret.normalize(); 119 | return ret; 120 | } 121 | 122 | Rational opBinary(string op)(Rational other) @safe pure nothrow const 123 | if (op == "/") 124 | { 125 | auto ret = Rational(num * other.den, den * other.num); 126 | ret.normalize(); 127 | return ret; 128 | } 129 | 130 | Rational opBinary(string op)(int value) @safe pure nothrow const 131 | if (op == "+" || op == "-" || op == "*" || op == "/") 132 | out 133 | { 134 | assert(isNormalized); 135 | } 136 | body 137 | { 138 | return mixin("this" ~ op ~ "Rational(value)"); 139 | } 140 | 141 | bool opEquals(Rational other) @safe pure nothrow const 142 | { 143 | return num == other.num && den == other.den; 144 | } 145 | 146 | bool opEquals(int value) @safe pure nothrow const 147 | { 148 | return num == value && den == 1; 149 | } 150 | 151 | int opCmp(Rational other) @safe pure nothrow const 152 | { 153 | immutable diff = (num / cast(double) den) - (other.num / cast(double) other.den); 154 | if (diff == 0) 155 | return 0; 156 | if (diff > 0) 157 | return 1; 158 | return -1; 159 | } 160 | 161 | int opCmp(int value) @safe pure nothrow const 162 | { 163 | return opCmp(Rational(value)); 164 | } 165 | 166 | T opCast(T)() @safe pure nothrow const 167 | if (isNumeric!T) 168 | { 169 | return num / cast(T) den; 170 | } 171 | 172 | void toString(scope void delegate(const(char)[]) sink) const 173 | { 174 | sink.formattedWrite!"%d"(num); 175 | if (den != 1) 176 | { 177 | sink("/"); 178 | sink.formattedWrite!"%d"(den); 179 | } 180 | } 181 | } 182 | 183 | private int gcd(int x, int y) @safe pure nothrow 184 | { 185 | if (x == 0 || y == 0) 186 | return 1; 187 | 188 | int tmp; 189 | int a = abs(x); 190 | int b = abs(y); 191 | while (a > 0) 192 | { 193 | tmp = a; 194 | a = b % a; 195 | b = tmp; 196 | } 197 | return b; 198 | } 199 | 200 | /// Struct describing properties of a dimension in a dimension vector. 201 | struct Dim 202 | { 203 | string symbol; /// The symbol of the dimension 204 | Rational power; /// The power of the dimension 205 | size_t rank = size_t.max; /// The rank of the dimension in the vector 206 | 207 | this(string symbol, Rational power, size_t rank = size_t.max) @safe pure nothrow 208 | { 209 | this.symbol = symbol; 210 | this.power = power; 211 | this.rank = rank; 212 | } 213 | 214 | this(string symbol, int power, size_t rank = size_t.max) @safe pure nothrow 215 | { 216 | this(symbol, Rational(power), rank); 217 | } 218 | 219 | int opCmp(Dim other) @safe pure nothrow const 220 | { 221 | if (rank == other.rank) 222 | { 223 | if (symbol < other.symbol) 224 | return -1; 225 | else if (symbol > other.symbol) 226 | return 1; 227 | else 228 | return 0; 229 | } 230 | else 231 | { 232 | if (rank < other.rank) 233 | return -1; 234 | else if (rank > other.rank) 235 | return 1; 236 | else 237 | assert(false); 238 | } 239 | } 240 | 241 | /// 242 | void toString(scope void delegate(const(char)[]) sink) const 243 | { 244 | if (power == 0) 245 | return; 246 | if (power == 1) 247 | sink(symbol); 248 | else 249 | { 250 | sink.formattedWrite!"%s"(symbol); 251 | sink("^"); 252 | sink.formattedWrite!"%s"(power); 253 | } 254 | } 255 | } 256 | 257 | private immutable(Dim)[] inverted(immutable(Dim)[] source) @safe pure nothrow 258 | { 259 | Dim[] target = source.dup; 260 | foreach (ref dim; target) 261 | dim.power = -dim.power; 262 | return target.immut; 263 | } 264 | 265 | private void insertAndSort(ref Dim[] list, string symbol, Rational power, size_t rank) @safe pure 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 | auto dim = Dim(symbol, power, rank); 288 | pos = list.countUntil!(d => d > dim); 289 | if (pos < 0) 290 | pos = list.length; 291 | list.insertInPlace(pos, dim); 292 | } 293 | assert(list.isSorted); 294 | } 295 | 296 | private immutable(Dim)[] immut(Dim[] source) @trusted pure nothrow 297 | { 298 | if (__ctfe) 299 | return source.idup; 300 | else 301 | return source.assumeUnique; 302 | } 303 | 304 | private immutable(Dim)[] insertSorted(immutable(Dim)[] source, string symbol, 305 | Rational power, size_t rank) @safe pure 306 | { 307 | if (power == 0) 308 | return source; 309 | 310 | if (!source.length) 311 | return [Dim(symbol, power, rank)].immut; 312 | 313 | Dim[] list = source.dup; 314 | insertAndSort(list, symbol, power, rank); 315 | return list.immut; 316 | } 317 | private immutable(Dim)[] insertSorted(immutable(Dim)[] source, immutable(Dim)[] other) @safe pure 318 | { 319 | Dim[] list = source.dup; 320 | foreach (dim; other) 321 | insertAndSort(list, dim.symbol, dim.power, dim.rank); 322 | return list.immut; 323 | } 324 | 325 | /// A vector of dimensions 326 | struct Dimensions 327 | { 328 | private: 329 | immutable(Dim)[] _dims; 330 | 331 | package(quantities): 332 | static Dimensions mono(string symbol, size_t rank) @safe pure nothrow 333 | { 334 | if (!symbol.length) 335 | return Dimensions(null); 336 | return Dimensions([Dim(symbol, 1, rank)].immut); 337 | } 338 | 339 | public: 340 | this(this) @safe pure nothrow 341 | { 342 | _dims = _dims.idup; 343 | } 344 | 345 | ref Dimensions opAssign()(auto ref const Dimensions other) @safe pure nothrow 346 | { 347 | _dims = other._dims.idup; 348 | return this; 349 | } 350 | 351 | /// The dimensions stored in this vector 352 | immutable(Dim)[] dims() @safe pure nothrow const 353 | { 354 | return _dims; 355 | } 356 | 357 | alias dims this; 358 | 359 | bool empty() @safe pure nothrow const 360 | { 361 | return _dims.empty; 362 | } 363 | 364 | Dimensions inverted() @safe pure nothrow const 365 | { 366 | return Dimensions(_dims.inverted); 367 | } 368 | 369 | Dimensions opUnary(string op)() @safe pure nothrow const 370 | if (op == "~") 371 | { 372 | return Dimensions(_dims.inverted); 373 | } 374 | Dimensions opBinary(string op)(const Dimensions other) @safe pure const 375 | if (op == "*") 376 | { 377 | return Dimensions(_dims.insertSorted(other._dims)); 378 | } 379 | 380 | Dimensions opBinary(string op)(const Dimensions other) @safe pure const 381 | if (op == "/") 382 | { 383 | return Dimensions(_dims.insertSorted(other._dims.inverted)); 384 | } 385 | Dimensions pow(Rational n) @safe pure nothrow const 386 | { 387 | if (n == 0) 388 | return Dimensions.init; 389 | 390 | auto list = _dims.dup; 391 | foreach (ref dim; list) 392 | dim.power = dim.power * n; 393 | return Dimensions(list.immut); 394 | } 395 | 396 | Dimensions pow(int n) @safe pure nothrow const 397 | { 398 | return pow(Rational(n)); 399 | } 400 | 401 | Dimensions powinverse(Rational n) @safe pure nothrow const 402 | { 403 | import std.exception : enforce; 404 | import std.string : format; 405 | 406 | auto list = _dims.dup; 407 | foreach (ref dim; list) 408 | dim.power = dim.power / n; 409 | return Dimensions(list.immut); 410 | } 411 | 412 | Dimensions powinverse(int n) @safe pure nothrow const 413 | { 414 | return powinverse(Rational(n)); 415 | } 416 | 417 | void toString(scope void delegate(const(char)[]) sink) const 418 | { 419 | sink.formattedWrite!"[%(%s %)]"(_dims); 420 | } 421 | } 422 | 423 | // Tests 424 | 425 | @("Rational") 426 | unittest 427 | { 428 | const r = Rational(6, -8); 429 | assert(r.text == "-3/4"); 430 | assert((+r).text == "-3/4"); 431 | assert((-r).text == "3/4"); 432 | 433 | const r1 = Rational(4, 3) + Rational(2, 5); 434 | assert(r1.text == "26/15"); 435 | const r2 = Rational(4, 3) - Rational(2, 5); 436 | assert(r2.text == "14/15"); 437 | const r3 = Rational(8, 7) * Rational(3, -2); 438 | assert(r3.text == "-12/7"); 439 | const r4 = Rational(8, 7) / Rational(3, -2); 440 | assert(r4.text == "-16/21"); 441 | 442 | auto r5 = Rational(4, 3); 443 | r5 += Rational(2, 5); 444 | assert(r5.text == "26/15"); 445 | 446 | auto r6 = Rational(8, 7); 447 | r6 /= Rational(2, -3); 448 | assert(r6.text == "-12/7"); 449 | 450 | assert(Rational(8, 7) == Rational(-16, -14)); 451 | assert(Rational(2, 5) < Rational(3, 7)); 452 | } 453 | 454 | @("Dim[].inverted") 455 | @safe pure nothrow unittest 456 | { 457 | auto list = [Dim("A", 2), Dim("B", -2)].idup; 458 | auto inv = [Dim("A", -2), Dim("B", 2)].idup; 459 | assert(list.inverted == inv); 460 | } 461 | @("Dim[].insertAndSort") 462 | @safe pure unittest 463 | { 464 | Dim[] list; 465 | list.insertAndSort("A", Rational(1), 1); 466 | assert(list == [Dim("A", 1, 1)]); 467 | list.insertAndSort("A", Rational(1), 1); 468 | assert(list == [Dim("A", 2, 1)]); 469 | list.insertAndSort("A", Rational(-2), 1); 470 | assert(list.length == 0); 471 | list.insertAndSort("B", Rational(1), 3); 472 | assert(list == [Dim("B", 1, 3)]); 473 | list.insertAndSort("C", Rational(1), 1); 474 | assert(Dim("C", 1, 1) < Dim("B", 1, 3)); 475 | assert(list == [Dim("C", 1, 1), Dim("B", 1, 3)]); 476 | } 477 | 478 | @("Dimensions *") 479 | @safe pure unittest 480 | { 481 | auto dim1 = Dimensions([Dim("a", 1), Dim("b", -2)]); 482 | auto dim2 = Dimensions([Dim("a", -1), Dim("c", 2)]); 483 | assert(dim1 * dim2 == Dimensions([Dim("b", -2), Dim("c", 2)])); 484 | } 485 | @("Dimensions /") 486 | @safe pure unittest 487 | { 488 | auto dim1 = Dimensions([Dim("a", 1), Dim("b", -2)]); 489 | auto dim2 = Dimensions([Dim("a", 1), Dim("c", 2)]); 490 | assert(dim1 / dim2 == Dimensions([Dim("b", -2), Dim("c", -2)])); 491 | } 492 | @("Dimensions pow") 493 | @safe pure nothrow unittest 494 | { 495 | auto dim = Dimensions([Dim("a", 5), Dim("b", -2)]); 496 | assert(dim.pow(Rational(2)) == Dimensions([Dim("a", 10), Dim("b", -4)])); 497 | assert(dim.pow(Rational(0)) == Dimensions.init); 498 | } 499 | 500 | @("Dimensions.powinverse") 501 | @safe pure nothrow unittest 502 | { 503 | auto dim = Dimensions([Dim("a", 6), Dim("b", -2)]); 504 | assert(dim.powinverse(Rational(2)) == Dimensions([Dim("a", 3), Dim("b", -1)])); 505 | } 506 | 507 | @("Dimensions.toString") 508 | unittest 509 | { 510 | auto dim = Dimensions([Dim("a", 1), Dim("b", -2)]); 511 | assert(dim.text == "[a b^-2]"); 512 | assert(Dimensions.init.text == "[]"); 513 | } 514 | -------------------------------------------------------------------------------- /source/quantities/internal/si.d: -------------------------------------------------------------------------------- 1 | /++ 2 | This module only contains the template mixins that define 3 | SI units, prefixes and symbols for use at compile-time and/or 4 | at run-time. 5 | 6 | Copyright: Copyright 2013-2018, Nicolas Sicard 7 | Authors: Nicolas Sicard 8 | License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Source: $(LINK https://github.com/biozic/quantities) 10 | +/ 11 | module quantities.internal.si; 12 | 13 | /++ 14 | Generates SI units, prefixes and several utility functions 15 | (parsing and formatting) usable at compile-time and/or at run-time. 16 | +/ 17 | mixin template SIDefinitions(N) 18 | { 19 | import quantities.common : prefix; 20 | import quantities.compiletime : Quantity, isQuantity, unit, square, cubic; 21 | import quantities.runtime : QVariant, isQVariantOrQuantity; 22 | import quantities.parsing : SymbolList, Parser; 23 | import std.conv : parse; 24 | import std.math : PI; 25 | import std.traits : isNumeric, isSomeString; 26 | 27 | static assert(isNumeric!N); 28 | 29 | /// The dimensionless unit 1. 30 | enum one = unit!(N, "", 0); 31 | 32 | /// Base SI units. 33 | enum meter = unit!(N, "L", 1); 34 | alias metre = meter; /// ditto 35 | enum kilogram = unit!(N, "M", 2); /// ditto 36 | enum second = unit!(N, "T", 3); /// ditto 37 | enum ampere = unit!(N, "I", 4); /// ditto 38 | enum kelvin = unit!(N, "Θ", 5); /// ditto 39 | enum mole = unit!(N, "N", 6); /// ditto 40 | enum candela = unit!(N, "J", 7); /// ditto 41 | 42 | /// Derived SI units 43 | enum radian = meter / meter; // ditto 44 | enum steradian = square(meter) / square(meter); /// ditto 45 | enum hertz = 1 / second; /// ditto 46 | enum newton = kilogram * meter / square(second); /// ditto 47 | enum pascal = newton / square(meter); /// ditto 48 | enum joule = newton * meter; /// ditto 49 | enum watt = joule / second; /// ditto 50 | enum coulomb = second * ampere; /// ditto 51 | enum volt = watt / ampere; /// ditto 52 | enum farad = coulomb / volt; /// ditto 53 | enum ohm = volt / ampere; /// ditto 54 | enum siemens = ampere / volt; /// ditto 55 | enum weber = volt * second; /// ditto 56 | enum tesla = weber / square(meter); /// ditto 57 | enum henry = weber / ampere; /// ditto 58 | enum celsius = kelvin; /// ditto 59 | enum lumen = candela / steradian; /// ditto 60 | enum lux = lumen / square(meter); /// ditto 61 | enum becquerel = 1 / second; /// ditto 62 | enum gray = joule / kilogram; /// ditto 63 | enum sievert = joule / kilogram; /// ditto 64 | enum katal = mole / second; /// ditto 65 | 66 | /// Units compatible with the SI 67 | enum gram = 1e-3 * kilogram; 68 | enum minute = 60 * second; /// ditto 69 | enum hour = 60 * minute; /// ditto 70 | enum day = 24 * hour; /// ditto 71 | enum degreeOfAngle = PI / 180 * radian; /// ditto 72 | enum minuteOfAngle = degreeOfAngle / 60; /// ditto 73 | enum secondOfAngle = minuteOfAngle / 60; /// ditto 74 | enum hectare = 1e4 * square(meter); /// ditto 75 | enum liter = 1e-3 * cubic(meter); /// ditto 76 | alias litre = liter; /// ditto 77 | enum ton = 1e3 * kilogram; /// ditto 78 | enum electronVolt = 1.60217653e-19 * joule; /// ditto 79 | enum dalton = 1.66053886e-27 * kilogram; /// ditto 80 | 81 | /// SI prefixes. 82 | alias yotta = prefix!1e24; 83 | alias zetta = prefix!1e21; /// ditto 84 | alias exa = prefix!1e18; /// ditto 85 | alias peta = prefix!1e15; /// ditto 86 | alias tera = prefix!1e12; /// ditto 87 | alias giga = prefix!1e9; /// ditto 88 | alias mega = prefix!1e6; /// ditto 89 | alias kilo = prefix!1e3; /// ditto 90 | alias hecto = prefix!1e2; /// ditto 91 | alias deca = prefix!1e1; /// ditto 92 | alias deci = prefix!1e-1; /// ditto 93 | alias centi = prefix!1e-2; /// ditto 94 | alias milli = prefix!1e-3; /// ditto 95 | alias micro = prefix!1e-6; /// ditto 96 | alias nano = prefix!1e-9; /// ditto 97 | alias pico = prefix!1e-12; /// ditto 98 | alias femto = prefix!1e-15; /// ditto 99 | alias atto = prefix!1e-18; /// ditto 100 | alias zepto = prefix!1e-21; /// ditto 101 | alias yocto = prefix!1e-24; /// ditto 102 | 103 | /// Predefined quantity type templates for SI quantities 104 | alias Dimensionless = typeof(one); 105 | alias Length = typeof(meter); 106 | alias Mass = typeof(kilogram); /// ditto 107 | alias Time = typeof(second); /// ditto 108 | alias ElectricCurrent = typeof(ampere); /// ditto 109 | alias Temperature = typeof(kelvin); /// ditto 110 | alias AmountOfSubstance = typeof(mole); /// ditto 111 | alias LuminousIntensity = typeof(candela); /// ditto 112 | 113 | alias Area = typeof(square(meter)); /// ditto 114 | alias Surface = Area; 115 | alias Volume = typeof(cubic(meter)); /// ditto 116 | alias Speed = typeof(meter / second); /// ditto 117 | alias Acceleration = typeof(meter / square(second)); /// ditto 118 | alias MassDensity = typeof(kilogram / cubic(meter)); /// ditto 119 | alias CurrentDensity = typeof(ampere / square(meter)); /// ditto 120 | alias MagneticFieldStrength = typeof(ampere / meter); /// ditto 121 | alias Concentration = typeof(mole / cubic(meter)); /// ditto 122 | alias MolarConcentration = Concentration; /// ditto 123 | alias MassicConcentration = typeof(kilogram / cubic(meter)); /// ditto 124 | alias Luminance = typeof(candela / square(meter)); /// ditto 125 | alias RefractiveIndex = typeof(kilogram); /// ditto 126 | 127 | alias Angle = typeof(radian); /// ditto 128 | alias SolidAngle = typeof(steradian); /// ditto 129 | alias Frequency = typeof(hertz); /// ditto 130 | alias Force = typeof(newton); /// ditto 131 | alias Pressure = typeof(pascal); /// ditto 132 | alias Energy = typeof(joule); /// ditto 133 | alias Work = Energy; /// ditto 134 | alias Heat = Energy; /// ditto 135 | alias Power = typeof(watt); /// ditto 136 | alias ElectricCharge = typeof(coulomb); /// ditto 137 | alias ElectricPotential = typeof(volt); /// ditto 138 | alias Capacitance = typeof(farad); /// ditto 139 | alias ElectricResistance = typeof(ohm); /// ditto 140 | alias ElectricConductance = typeof(siemens); /// ditto 141 | alias MagneticFlux = typeof(weber); /// ditto 142 | alias MagneticFluxDensity = typeof(tesla); /// ditto 143 | alias Inductance = typeof(henry); /// ditto 144 | alias LuminousFlux = typeof(lumen); /// ditto 145 | alias Illuminance = typeof(lux); /// ditto 146 | alias CelsiusTemperature = typeof(celsius); /// ditto 147 | alias Radioactivity = typeof(becquerel); /// ditto 148 | alias AbsorbedDose = typeof(gray); /// ditto 149 | alias DoseEquivalent = typeof(sievert); /// ditto 150 | alias CatalyticActivity = typeof(katal); /// ditto 151 | 152 | /// A list of common SI symbols and prefixes 153 | // dfmt off 154 | enum siSymbolList = SymbolList!N() 155 | .addUnit("m", meter) 156 | .addUnit("kg", kilogram) 157 | .addUnit("s", second) 158 | .addUnit("A", ampere) 159 | .addUnit("K", kelvin) 160 | .addUnit("mol", mole) 161 | .addUnit("cd", candela) 162 | .addUnit("rad", radian) 163 | .addUnit("sr", steradian) 164 | .addUnit("Hz", hertz) 165 | .addUnit("N", newton) 166 | .addUnit("Pa", pascal) 167 | .addUnit("J", joule) 168 | .addUnit("W", watt) 169 | .addUnit("C", coulomb) 170 | .addUnit("V", volt) 171 | .addUnit("F", farad) 172 | .addUnit("Ω", ohm) 173 | .addUnit("S", siemens) 174 | .addUnit("Wb", weber) 175 | .addUnit("T", tesla) 176 | .addUnit("H", henry) 177 | .addUnit("lm", lumen) 178 | .addUnit("lx", lux) 179 | .addUnit("Bq", becquerel) 180 | .addUnit("Gy", gray) 181 | .addUnit("Sv", sievert) 182 | .addUnit("kat", katal) 183 | .addUnit("g", gram) 184 | .addUnit("min", minute) 185 | .addUnit("h", hour) 186 | .addUnit("d", day) 187 | .addUnit("l", liter) 188 | .addUnit("L", liter) 189 | .addUnit("t", ton) 190 | .addUnit("eV", electronVolt) 191 | .addUnit("Da", dalton) 192 | .addPrefix("Y", 1e24) 193 | .addPrefix("Z", 1e21) 194 | .addPrefix("E", 1e18) 195 | .addPrefix("P", 1e15) 196 | .addPrefix("T", 1e12) 197 | .addPrefix("G", 1e9) 198 | .addPrefix("M", 1e6) 199 | .addPrefix("k", 1e3) 200 | .addPrefix("h", 1e2) 201 | .addPrefix("da", 1e1) 202 | .addPrefix("d", 1e-1) 203 | .addPrefix("c", 1e-2) 204 | .addPrefix("m", 1e-3) 205 | .addPrefix("µ", 1e-6) 206 | .addPrefix("n", 1e-9) 207 | .addPrefix("p", 1e-12) 208 | .addPrefix("f", 1e-15) 209 | .addPrefix("a", 1e-18) 210 | .addPrefix("z", 1e-21) 211 | .addPrefix("y", 1e-24); 212 | // dfmt on 213 | 214 | /// A list of common SI symbols and prefixes 215 | static 216 | { 217 | SymbolList!N siSymbols; 218 | Parser!(N, (ref s) => parse!N(s)) siParser; 219 | } 220 | static this() 221 | { 222 | siSymbols = siSymbolList; 223 | siParser = typeof(siParser)(siSymbols); 224 | } 225 | 226 | /++ 227 | Parses a statically-typed Quantity from a string at run time. 228 | 229 | Throws a DimensionException if the parsed quantity doesn't have the same 230 | dimensions as Q. 231 | 232 | Params: 233 | Q = the type of the returned quantity. 234 | str = the string to parse. 235 | +/ 236 | Q parseSI(Q, S)(S str) 237 | if (isQuantity!Q && isSomeString!S) 238 | { 239 | return Q(siParser.parse(str)); 240 | } 241 | /// 242 | unittest 243 | { 244 | alias Time = typeof(second); 245 | Time t = parseSI!Time("90 min"); 246 | assert(t == 90 * minute); 247 | t = parseSI!Time("h"); 248 | assert(t == 1 * hour); 249 | } 250 | 251 | /++ 252 | Creates a Quantity from a string at compile-time. 253 | +/ 254 | template si(string str) 255 | { 256 | enum ctSIParser = Parser!(N, (ref s) => parse!N(s))(siSymbolList); 257 | enum qty = ctSIParser.parse(str); 258 | enum si = Quantity!(N, qty.dimensions())(qty); 259 | } 260 | /// 261 | unittest 262 | { 263 | alias Time = typeof(second); 264 | enum t = si!"90 min"; 265 | assert(is(typeof(t) == Time)); 266 | assert(si!"h" == 60 * 60 * second); 267 | } 268 | 269 | /++ 270 | Parses a string for a quantity at run time. 271 | 272 | Params: 273 | str = the string to parse. 274 | +/ 275 | QVariant!N parseSI(S)(S str) 276 | if (isSomeString!S) 277 | { 278 | if (__ctfe) 279 | { 280 | import quantities.parsing : Parser; 281 | import std.conv : parse; 282 | 283 | auto ctSIParser = Parser!(N, (ref s) => parse!N(s))(siSymbolList); 284 | return ctSIParser.parse(str); 285 | } 286 | return siParser.parse(str); 287 | } 288 | /// 289 | unittest 290 | { 291 | auto t = parseSI("90 min"); 292 | assert(t == 90 * minute); 293 | t = parseSI("h"); 294 | assert(t == 1 * hour); 295 | 296 | auto v = parseSI("2"); 297 | assert(v == (2 * meter) / meter); 298 | } 299 | 300 | /++ 301 | A struct able to format a SI quantity. 302 | +/ 303 | struct SIFormatter(S) 304 | if (isSomeString!S) 305 | { 306 | import std.range : ElementEncodingType; 307 | 308 | alias Char = ElementEncodingType!S; 309 | 310 | private 311 | { 312 | S fmt; 313 | QVariant!double unit; 314 | } 315 | 316 | /++ 317 | Creates the formatter. 318 | 319 | Params: 320 | format = The format string. Must start with a format specification 321 | for the value of the quantity (a numeric type), that must be 322 | followed by the symbol of a SI unit. 323 | +/ 324 | this(S format) 325 | { 326 | import std.format : FormatSpec; 327 | import std.array : appender; 328 | 329 | fmt = format; 330 | auto spec = FormatSpec!Char(format); 331 | auto app = appender!S; 332 | spec.writeUpToNextSpec(app); 333 | unit = parseSI(spec.trailing); 334 | } 335 | 336 | void write(Writer, Q)(auto ref Writer writer, auto ref Q quantity) const 337 | if (isQVariantOrQuantity!Q) 338 | { 339 | import std.format : formattedWrite; 340 | 341 | formattedWrite(writer, fmt, quantity.value(unit)); 342 | } 343 | } 344 | 345 | /++ 346 | Formats a SI quantity according to a format string known at run-time. 347 | 348 | Params: 349 | format = The format string. Must start with a format specification 350 | for the value of the quantity (a numeric type), that must be 351 | followed by the symbol of a SI unit. 352 | quantity = The quantity that must be formatted. 353 | +/ 354 | S siFormat(S, Q)(S format, Q quantity) 355 | if (isSomeString!S && isQVariantOrQuantity!Q) 356 | { 357 | import std.array : appender; 358 | 359 | auto formatter = SIFormatter!S(format); 360 | auto app = appender!S; 361 | formatter.write(app, quantity); 362 | return app.data; 363 | } 364 | /// 365 | unittest 366 | { 367 | QVariant!double speed = 12.5 * kilo(meter) / hour; 368 | assert("%.2f m/s".siFormat(speed) == "3.47 m/s"); 369 | } 370 | 371 | /++ 372 | Formats a SI quantity according to a format string known at compile-time. 373 | 374 | Params: 375 | format = The format string. Must start with a format specification 376 | for the value of the quantity (a numeric type), that must be 377 | followed by the symbol of a SI unit. 378 | quantity = The quantity that must be formatted. 379 | +/ 380 | auto siFormat(alias format, Q)(Q quantity) 381 | if (isSomeString!(typeof(format)) && isQVariantOrQuantity!Q) 382 | { 383 | return siFormat(format, quantity); 384 | } 385 | /// 386 | unittest 387 | { 388 | enum speed = 12.5 * kilo(meter) / hour; 389 | assert(siFormat!"%.2f m/s"(speed) == "3.47 m/s"); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /source/quantities/package.d: -------------------------------------------------------------------------------- 1 | /++ 2 | The purpose of this small library is to perform automatic compile-time or 3 | run-time dimensional checking when dealing with quantities and units. 4 | 5 | There is no actual distinction between units and quantities, so there are no 6 | distinct quantity and unit types. All operations are actually done on 7 | quantities. For example, `meter` is both the unit _meter_ and the quantity _1m_. 8 | New quantities can be derived from other ones using operators or dedicated 9 | functions. 10 | 11 | Quantities can be parsed from strings at run time and compile time. 12 | 13 | The main SI units and prefixes are predefined. Units with other dimensions can 14 | be defined by the user. 15 | 16 | Copyright: Copyright 2013-2018, Nicolas Sicard. 17 | License: Boost License 1.0. 18 | +/ 19 | module quantities; 20 | 21 | public import quantities.compiletime; 22 | public import quantities.runtime; 23 | public import quantities.common; 24 | public import quantities.parsing; 25 | public import quantities.si; 26 | 27 | /// Synopsis at compile-time 28 | @("Synopsis at compile-time") 29 | unittest 30 | { 31 | import quantities.compiletime; 32 | import quantities.si; 33 | 34 | // Define a quantity from SI units 35 | auto distance = 384_400 * kilo(meter); 36 | 37 | // Define a quantity from a string 38 | auto speed = si!"299_792_458 m/s"; 39 | // Define a type for a quantity 40 | alias Speed = typeof(speed); 41 | 42 | // Calculations on quantities 43 | auto calculateTime(Length d, Speed s) 44 | { 45 | return d / s; 46 | } 47 | Time time = calculateTime(distance, speed); 48 | 49 | // Dimensions are checked at compile time for consistency 50 | assert(!__traits(compiles, distance + speed)); 51 | 52 | // Format a quantity with a format specification known at compile-time 53 | assert(siFormat!"%.3f s"(time) == "1.282 s"); 54 | } 55 | 56 | /// Synopsis at run-time 57 | @("Synopsis at run-time") 58 | unittest 59 | { 60 | import quantities.runtime; 61 | import quantities.si; 62 | import std.exception : assertThrown; 63 | 64 | // Define a quantity from SI units (using the helper function `qVariant`) 65 | auto distance = qVariant(384_400 * kilo(meter)); 66 | 67 | // Define a quantity from a string 68 | auto speed = parseSI("299_792_458 m/s"); 69 | 70 | // Calculations on quantities (checked at compile time for consistency) 71 | QVariant!double calculateTime(QVariant!double d, QVariant!double s) 72 | { 73 | return d / s; 74 | } 75 | auto time = calculateTime(distance, speed); 76 | 77 | // Dimensions are checked at run time for consistency 78 | assertThrown!DimensionException(distance + speed); 79 | 80 | // Format a quantity with a format specification known at run-time 81 | assert(siFormat("%.3f s", time) == "1.282 s"); 82 | } 83 | -------------------------------------------------------------------------------- /source/quantities/parsing.d: -------------------------------------------------------------------------------- 1 | /++ 2 | This module defines functions to parse units and quantities. 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.parsing; 10 | 11 | import quantities.internal.dimensions; 12 | import quantities.runtime; 13 | import quantities.compiletime; 14 | import std.conv : parse; 15 | import std.exception : basicExceptionCtors, enforce; 16 | import std.format : format; 17 | import std.traits : isNumeric, isSomeString; 18 | 19 | /++ 20 | Contains the symbols of the units and the prefixes that a parser can handle. 21 | +/ 22 | struct SymbolList(N) 23 | if (isNumeric!N) 24 | { 25 | static assert(isNumeric!N, "Incompatible type: " ~ N.stringof); 26 | 27 | package 28 | { 29 | QVariant!N[string] units; 30 | N[string] prefixes; 31 | size_t maxPrefixLength; 32 | } 33 | 34 | /// Adds (or replaces) a unit in the list 35 | auto addUnit(Q)(string symbol, Q unit) 36 | if (isQVariantOrQuantity!Q) 37 | { 38 | static if (isQVariant!Q) 39 | units[symbol] = unit; 40 | else static if (isQuantity!Q) 41 | units[symbol] = unit.qVariant; 42 | else 43 | static assert(false); 44 | return this; 45 | } 46 | 47 | /// Adds (or replaces) a prefix in the list 48 | auto addPrefix(N)(string symbol, N factor) 49 | if (isNumeric!N) 50 | { 51 | prefixes[symbol] = factor; 52 | if (symbol.length > maxPrefixLength) 53 | maxPrefixLength = symbol.length; 54 | return this; 55 | } 56 | } 57 | 58 | /++ 59 | A quantity parser. 60 | 61 | Params: 62 | N = The numeric type of the quantities. 63 | numberParser = a function that takes a reference to any kind of string and 64 | returns the parsed number. 65 | +/ 66 | struct Parser(N, alias numberParser = (ref s) => parse!N(s)) 67 | if (isNumeric!N) 68 | { 69 | /// A list of registered symbols for units and prefixes. 70 | SymbolList!N symbolList; 71 | 72 | /++ 73 | Parses a QVariant from str. 74 | +/ 75 | QVariant!N parse(S)(S str) 76 | if (isSomeString!S) 77 | { 78 | return parseQuantityImpl!(N, numberParser)(str, symbolList); 79 | } 80 | } 81 | /// 82 | unittest 83 | { 84 | // From http://en.wikipedia.org/wiki/List_of_humorous_units_of_measurement 85 | 86 | import std.conv : parse; 87 | 88 | auto century = unit!real("T"); 89 | alias LectureLength = typeof(century); 90 | 91 | auto symbolList = SymbolList!real().addUnit("Cy", century).addPrefix("µ", 1e-6L); 92 | alias numberParser = (ref s) => parse!real(s); 93 | auto parser = Parser!(real, numberParser)(symbolList); 94 | 95 | auto timing = 1e-6L * century; 96 | assert(timing == parser.parse("1 µCy")); 97 | } 98 | 99 | /// Exception thrown when parsing encounters an unexpected token. 100 | class ParsingException : Exception 101 | { 102 | mixin basicExceptionCtors; 103 | } 104 | 105 | package(quantities): 106 | 107 | QVariant!N parseQuantityImpl(N, alias numberParser, S)(S input, SymbolList!N symbolList) 108 | if (isSomeString!S) 109 | { 110 | import std.range.primitives : empty; 111 | 112 | N value; 113 | try 114 | value = numberParser(input); 115 | catch (Exception) 116 | value = 1; 117 | 118 | if (input.empty) 119 | return QVariant!N(value, Dimensions.init); 120 | 121 | auto parser = QuantityParser!(N, S)(input, symbolList); 122 | return value * parser.parsedQuantity(); 123 | } 124 | 125 | // A parser that can parse a text for a unit or a quantity 126 | struct QuantityParser(N, S) 127 | if (isNumeric!N && isSomeString!S) 128 | { 129 | import std.conv : to; 130 | import std.exception : enforce; 131 | import std.format : format; 132 | import std.range.primitives : empty, front, popFront; 133 | 134 | private 135 | { 136 | S input; 137 | SymbolList!N symbolList; 138 | Token[] tokens; 139 | } 140 | 141 | this(S input, SymbolList!N symbolList) 142 | { 143 | this.input = input; 144 | this.symbolList = symbolList; 145 | lex(input); 146 | } 147 | 148 | QVariant!N parsedQuantity() 149 | { 150 | return parseCompoundUnit(); 151 | } 152 | 153 | QVariant!N parseCompoundUnit(bool inParens = false) 154 | { 155 | QVariant!N ret = parseExponentUnit(); 156 | if (tokens.empty || (inParens && tokens.front.type == Tok.rparen)) 157 | return ret; 158 | 159 | do 160 | { 161 | check(); 162 | auto cur = tokens.front; 163 | 164 | bool multiply = true; 165 | if (cur.type == Tok.div) 166 | multiply = false; 167 | 168 | if (cur.type == Tok.mul || cur.type == Tok.div) 169 | { 170 | advance(); 171 | check(); 172 | cur = tokens.front; 173 | } 174 | 175 | QVariant!N rhs = parseExponentUnit(); 176 | if (multiply) 177 | ret *= rhs; 178 | else 179 | ret /= rhs; 180 | 181 | if (tokens.empty || (inParens && tokens.front.type == Tok.rparen)) 182 | break; 183 | 184 | cur = tokens.front; 185 | } 186 | while (!tokens.empty); 187 | 188 | return ret; 189 | } 190 | 191 | QVariant!N parseExponentUnit() 192 | { 193 | QVariant!N ret = parseUnit(); 194 | 195 | // If no exponent is found 196 | if (tokens.empty) 197 | return ret; 198 | 199 | // The next token should be '^', an integer or a superior integer 200 | auto next = tokens.front; 201 | if (next.type != Tok.exp && next.type != Tok.integer && next.type != Tok.supinteger) 202 | return ret; 203 | 204 | // Skip the '^' if present, and expect an integer 205 | if (next.type == Tok.exp) 206 | advance(Tok.integer); 207 | 208 | Rational r = parseRationalOrInteger(); 209 | return ret ^^ r; 210 | } 211 | 212 | Rational parseRationalOrInteger() 213 | { 214 | int num = parseInteger(); 215 | int den = 1; 216 | if (tokens.length && tokens.front.type == Tok.div) 217 | { 218 | advance(); 219 | den = parseInteger(); 220 | } 221 | return Rational(num, den); 222 | } 223 | 224 | int parseInteger() 225 | { 226 | check(Tok.integer, Tok.supinteger); 227 | int n = tokens.front.integer; 228 | if (tokens.length) 229 | advance(); 230 | return n; 231 | } 232 | 233 | QVariant!N parseUnit() 234 | { 235 | if (!tokens.length) 236 | return QVariant!N(1, Dimensions.init); 237 | 238 | if (tokens.front.type == Tok.lparen) 239 | { 240 | advance(); 241 | auto ret = parseCompoundUnit(true); 242 | check(Tok.rparen); 243 | advance(); 244 | return ret; 245 | } 246 | else 247 | return parsePrefixUnit(); 248 | } 249 | 250 | QVariant!N parsePrefixUnit() 251 | { 252 | check(Tok.symbol); 253 | auto str = input[tokens.front.begin .. tokens.front.end].to!string; 254 | if (tokens.length) 255 | advance(); 256 | 257 | // Try a standalone unit symbol (no prefix) 258 | auto uptr = str in symbolList.units; 259 | if (uptr) 260 | return *uptr; 261 | 262 | // Try with prefixes, the longest prefix first 263 | N* factor; 264 | for (size_t i = symbolList.maxPrefixLength; i > 0; i--) 265 | { 266 | if (str.length >= i) 267 | { 268 | string prefix = str[0 .. i].to!string; 269 | factor = prefix in symbolList.prefixes; 270 | if (factor) 271 | { 272 | string unit = str[i .. $].to!string; 273 | enforce!ParsingException(unit.length, 274 | "Expecting a unit after the prefix " ~ prefix); 275 | uptr = unit in symbolList.units; 276 | if (uptr) 277 | return *factor * *uptr; 278 | } 279 | } 280 | } 281 | 282 | throw new ParsingException("Unknown unit symbol: '%s'".format(str)); 283 | } 284 | 285 | enum Tok 286 | { 287 | none, 288 | symbol, 289 | mul, 290 | div, 291 | exp, 292 | integer, 293 | supinteger, 294 | rparen, 295 | lparen 296 | } 297 | 298 | struct Token 299 | { 300 | Tok type; 301 | size_t begin; 302 | size_t end; 303 | int integer = int.max; 304 | } 305 | 306 | void lex(S input) @safe 307 | { 308 | import std.array : appender; 309 | import std.conv : parse; 310 | import std.exception : enforce; 311 | import std.utf : codeLength; 312 | 313 | enum State 314 | { 315 | none, 316 | symbol, 317 | integer, 318 | supinteger 319 | } 320 | 321 | auto tokapp = appender(tokens); 322 | size_t i, j; 323 | State state = State.none; 324 | auto intapp = appender!string; 325 | 326 | void pushToken(Tok type) 327 | { 328 | tokapp.put(Token(type, i, j)); 329 | i = j; 330 | state = State.none; 331 | } 332 | 333 | void pushInteger(Tok type) 334 | { 335 | int n; 336 | auto slice = intapp.data; 337 | try 338 | { 339 | n = parse!int(slice); 340 | assert(slice.empty); 341 | } 342 | catch (Exception) 343 | throw new ParsingException("Unexpected integer format: %s".format(slice)); 344 | 345 | tokapp.put(Token(type, i, j, n)); 346 | i = j; 347 | state = State.none; 348 | intapp = appender!string; 349 | } 350 | 351 | void push() 352 | { 353 | if (state == State.symbol) 354 | pushToken(Tok.symbol); 355 | else if (state == State.integer) 356 | pushInteger(Tok.integer); 357 | else if (state == State.supinteger) 358 | pushInteger(Tok.supinteger); 359 | } 360 | 361 | foreach (dchar cur; input) 362 | { 363 | auto len = cur.codeLength!char; 364 | switch (cur) 365 | { 366 | case ' ': 367 | case '\t': 368 | case '\u00A0': 369 | case '\u2000': .. case '\u200A': 370 | case '\u202F': 371 | case '\u205F': 372 | push(); 373 | j += len; 374 | i = j; 375 | break; 376 | 377 | case '(': 378 | push(); 379 | j += len; 380 | pushToken(Tok.lparen); 381 | break; 382 | case ')': 383 | push(); 384 | j += len; 385 | pushToken(Tok.rparen); 386 | break; 387 | 388 | case '*': // Asterisk 389 | case '.': // Dot 390 | case '\u00B7': // Middle dot (·) 391 | case '\u00D7': // Multiplication sign (×) 392 | case '\u2219': // Bullet operator (∙) 393 | case '\u22C5': // Dot operator (⋅) 394 | case '\u2022': // Bullet (•) 395 | case '\u2715': // Multiplication X (✕) 396 | push(); 397 | j += len; 398 | pushToken(Tok.mul); 399 | break; 400 | 401 | case '/': // Slash 402 | case '\u00F7': // Division sign (÷) 403 | case '\u2215': // Division slash (∕) 404 | push(); 405 | j += len; 406 | pushToken(Tok.div); 407 | break; 408 | 409 | case '^': 410 | push(); 411 | j += len; 412 | pushToken(Tok.exp); 413 | break; 414 | 415 | case '-': // Hyphen 416 | case '\u2212': // Minus sign (−) 417 | case '\u2012': // Figure dash (‒) 418 | case '\u2013': // En dash (–) 419 | intapp.put('-'); 420 | goto PushIntChar; 421 | case '+': // Plus sign 422 | intapp.put('+'); 423 | goto PushIntChar; 424 | case '0': .. case '9': 425 | intapp.put(cur); 426 | PushIntChar: 427 | if (state != State.integer) 428 | push(); 429 | state = State.integer; 430 | j += len; 431 | break; 432 | 433 | case '⁰': 434 | intapp.put('0'); 435 | goto PushSupIntChar; 436 | case '¹': 437 | intapp.put('1'); 438 | goto PushSupIntChar; 439 | case '²': 440 | intapp.put('2'); 441 | goto PushSupIntChar; 442 | case '³': 443 | intapp.put('3'); 444 | goto PushSupIntChar; 445 | case '⁴': 446 | intapp.put('4'); 447 | goto PushSupIntChar; 448 | case '⁵': 449 | intapp.put('5'); 450 | goto PushSupIntChar; 451 | case '⁶': 452 | intapp.put('6'); 453 | goto PushSupIntChar; 454 | case '⁷': 455 | intapp.put('7'); 456 | goto PushSupIntChar; 457 | case '⁸': 458 | intapp.put('8'); 459 | goto PushSupIntChar; 460 | case '⁹': 461 | intapp.put('9'); 462 | goto PushSupIntChar; 463 | case '⁻': 464 | intapp.put('-'); 465 | goto PushSupIntChar; 466 | case '⁺': 467 | intapp.put('+'); 468 | PushSupIntChar: 469 | if (state != State.supinteger) 470 | push(); 471 | state = State.supinteger; 472 | j += len; 473 | break; 474 | 475 | default: 476 | if (state == State.integer || state == State.supinteger) 477 | push(); 478 | state = State.symbol; 479 | j += len; 480 | break; 481 | } 482 | } 483 | push(); 484 | tokens = tokapp.data; 485 | } 486 | 487 | void advance(Types...)(Types types) 488 | { 489 | enforce!ParsingException(!tokens.empty, "Unexpected end of input"); 490 | tokens.popFront(); 491 | 492 | static if (Types.length) 493 | check(types); 494 | } 495 | 496 | void check() 497 | { 498 | enforce!ParsingException(tokens.length, "Unexpected end of input"); 499 | } 500 | 501 | void check(Tok tok) 502 | { 503 | check(); 504 | enforce!ParsingException(tokens[0].type == tok, 505 | format("Found '%s' while expecting %s", input[tokens[0].begin .. tokens[0].end], 506 | tok)); 507 | } 508 | 509 | void check(Tok tok1, Tok tok2) 510 | { 511 | check(); 512 | enforce!ParsingException(tokens[0].type == tok1 || tokens[0].type == tok2, 513 | format("Found '%s' while expecting %s or %s", 514 | input[tokens[0].begin .. tokens[0].end], tok1, tok2)); 515 | } 516 | } 517 | 518 | // Tests 519 | 520 | @("Generic parsing") 521 | unittest 522 | { 523 | import std.exception : assertThrown; 524 | 525 | auto meter = unit!double("L"); 526 | auto kilogram = unit!double("M"); 527 | auto second = unit!double("T"); 528 | auto one = meter / meter; 529 | auto unknown = one; 530 | 531 | auto siSL = SymbolList!double().addUnit("m", meter).addUnit("kg", kilogram) 532 | .addUnit("s", second).addPrefix("c", 0.01L).addPrefix("m", 0.001L); 533 | 534 | bool checkParse(S, Q)(S input, Q quantity) 535 | { 536 | import std.conv : parse; 537 | 538 | return parseQuantityImpl!(double, (ref s) => parse!double(s))(input, siSL) == quantity; 539 | } 540 | 541 | assert(checkParse("1 m ", meter)); 542 | assert(checkParse("1m", meter)); 543 | assert(checkParse("1 mm", 0.001 * meter)); 544 | assert(checkParse("1 m2", meter * meter)); 545 | assert(checkParse("1 m^-1", 1 / meter)); 546 | assert(checkParse("1 m-1", 1 / meter)); 547 | assert(checkParse("1 m^1/1", meter)); 548 | assert(checkParse("1 m^-1/1", 1 / meter)); 549 | assert(checkParse("1 m²", meter * meter)); 550 | assert(checkParse("1 m⁺²", meter * meter)); 551 | assert(checkParse("1 m⁻¹", 1 / meter)); 552 | assert(checkParse("1 (m)", meter)); 553 | assert(checkParse("1 (m^-1)", 1 / meter)); 554 | assert(checkParse("1 ((m)^-1)^-1", meter)); 555 | assert(checkParse("1 (s/(s/m))", meter)); 556 | assert(checkParse("1 m*m", meter * meter)); 557 | assert(checkParse("1 m m", meter * meter)); 558 | assert(checkParse("1 m.m", meter * meter)); 559 | assert(checkParse("1 m⋅m", meter * meter)); 560 | assert(checkParse("1 m×m", meter * meter)); 561 | assert(checkParse("1 m/m", meter / meter)); 562 | assert(checkParse("1 m÷m", meter / meter)); 563 | assert(checkParse("1 m.s", second * meter)); 564 | assert(checkParse("1 m s", second * meter)); 565 | assert(checkParse("1 m²s", meter * meter * second)); 566 | assert(checkParse("1 m*m/m", meter)); 567 | assert(checkParse("0.8 m⁰", 0.8 * one)); 568 | assert(checkParse("0.8", 0.8 * one)); 569 | assert(checkParse("0.8 ", 0.8 * one)); 570 | 571 | assertThrown!ParsingException(checkParse("1 c m", unknown)); 572 | assertThrown!ParsingException(checkParse("1 c", unknown)); 573 | assertThrown!ParsingException(checkParse("1 Qm", unknown)); 574 | assertThrown!ParsingException(checkParse("1 m + m", unknown)); 575 | assertThrown!ParsingException(checkParse("1 m/", unknown)); 576 | assertThrown!ParsingException(checkParse("1 m^", unknown)); 577 | assertThrown!ParsingException(checkParse("1 m^m", unknown)); 578 | assertThrown!ParsingException(checkParse("1 m ) m", unknown)); 579 | assertThrown!ParsingException(checkParse("1 m * m) m", unknown)); 580 | assertThrown!ParsingException(checkParse("1 m^²", unknown)); 581 | assertThrown!ParsingException(checkParse("1-⁺⁵", unknown)); 582 | } 583 | -------------------------------------------------------------------------------- /source/quantities/runtime.d: -------------------------------------------------------------------------------- 1 | /++ 2 | This module defines dimensionally variant quantities, mainly for use at run-time. 3 | 4 | The dimensions are stored in a field, along with the numerical value of the 5 | quantity. Operations and function calls fail if they are not dimensionally 6 | consistent, by throwing a `DimensionException`. 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.runtime; 14 | 15 | /// 16 | unittest 17 | { 18 | import quantities.runtime; 19 | import quantities.si; 20 | import std.format : format; 21 | import std.math : approxEqual; 22 | 23 | // Note: the types of the predefined SI units (gram, mole, liter...) 24 | // are Quantity instances, not QVariant instance. 25 | 26 | // Introductory example 27 | { 28 | // I have to make a new solution at the concentration of 5 mmol/L 29 | QVariant!double concentration = 5.0 * milli(mole) / liter; 30 | 31 | // The final volume is 100 ml. 32 | QVariant!double volume = 100.0 * milli(liter); 33 | 34 | // The molar mass of my compound is 118.9 g/mol 35 | QVariant!double molarMass = 118.9 * gram / mole; 36 | 37 | // What mass should I weigh? 38 | QVariant!double mass = concentration * volume * molarMass; 39 | assert(format("%s", mass) == "5.945e-05 [M]"); 40 | // Wait! That's not really useful! 41 | assert(siFormat!"%.1f mg"(mass) == "59.5 mg"); 42 | } 43 | 44 | // Working with predefined units 45 | { 46 | QVariant!double distance = 384_400 * kilo(meter); // From Earth to Moon 47 | QVariant!double speed = 299_792_458 * meter / second; // Speed of light 48 | QVariant!double time = distance / speed; 49 | assert(time.siFormat!"%.3f s" == "1.282 s"); 50 | } 51 | 52 | // Dimensional correctness 53 | { 54 | import std.exception : assertThrown; 55 | 56 | QVariant!double mass = 4 * kilogram; 57 | assertThrown!DimensionException(mass + meter); 58 | assertThrown!DimensionException(mass == 1.2); 59 | } 60 | 61 | // Create a new unit from the predefined ones 62 | { 63 | QVariant!double inch = 2.54 * centi(meter); 64 | QVariant!double mile = 1609 * meter; 65 | assert(mile.value(inch).approxEqual(63_346)); // inches in a mile 66 | // NB. Cannot use siFormatter, because inches are not SI units 67 | } 68 | 69 | // Create a new unit with new dimensions 70 | { 71 | // Create a new base unit of currency 72 | QVariant!double euro = unit!double("C"); // C is the chosen dimension symol (for currency...) 73 | 74 | QVariant!double dollar = euro / 1.35; 75 | QVariant!double price = 2000 * dollar; 76 | assert(price.value(euro).approxEqual(1481)); // Price in euros 77 | } 78 | 79 | // Run-time parsing 80 | { 81 | auto data = ["distance-to-the-moon" : "384_400 km", "speed-of-light" : "299_792_458 m/s"]; 82 | QVariant!double distance = parseSI(data["distance-to-the-moon"]); 83 | QVariant!double speed = parseSI(data["speed-of-light"]); 84 | QVariant!double time = distance / speed; 85 | } 86 | } 87 | 88 | import quantities.internal.dimensions; 89 | import quantities.common; 90 | import quantities.compiletime; 91 | 92 | import std.conv; 93 | import std.exception; 94 | import std.format; 95 | import std.math; 96 | import std.string; 97 | import std.traits; 98 | 99 | /++ 100 | Exception thrown when operating on two units that are not interconvertible. 101 | +/ 102 | class DimensionException : Exception 103 | { 104 | /// Holds the dimensions of the quantity currently operated on 105 | Dimensions thisDim; 106 | /// Holds the dimensions of the eventual other operand 107 | Dimensions otherDim; 108 | 109 | mixin basicExceptionCtors; 110 | 111 | this(string msg, Dimensions thisDim, Dimensions otherDim, 112 | string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow 113 | { 114 | super(msg, file, line, next); 115 | this.thisDim = thisDim; 116 | this.otherDim = otherDim; 117 | } 118 | } 119 | /// 120 | unittest 121 | { 122 | import std.exception : assertThrown; 123 | 124 | enum meter = unit!double("L"); 125 | enum second = unit!double("T"); 126 | assertThrown!DimensionException(meter + second); 127 | } 128 | 129 | /++ 130 | A dimensionnaly variant quantity. 131 | 132 | Params: 133 | N = the numeric type of the quantity. 134 | 135 | See_Also: 136 | QVariant has the same public members and overloaded operators as Quantity. 137 | +/ 138 | struct QVariant(N) 139 | { 140 | static assert(isNumeric!N, "Incompatible type: " ~ N.stringof); 141 | 142 | private: 143 | N _value; 144 | Dimensions _dimensions; 145 | 146 | void checkDim(Dimensions dim) @safe pure const 147 | { 148 | enforce(_dimensions == dim, 149 | new DimensionException("Incompatible dimensions", _dimensions, dim)); 150 | } 151 | 152 | void checkDimensionless() @safe pure const 153 | { 154 | enforce(_dimensions.empty, new DimensionException("Not dimensionless", 155 | _dimensions, Dimensions.init)); 156 | } 157 | 158 | package(quantities): 159 | alias valueType = N; 160 | 161 | N rawValue() const 162 | { 163 | return _value; 164 | } 165 | 166 | public: 167 | // Creates a new quantity with non-empty dimensions 168 | this(T)(T scalar, const Dimensions dim) 169 | if (isNumeric!T) 170 | { 171 | _value = scalar; 172 | _dimensions = dim; 173 | } 174 | 175 | /// Creates a new quantity from another one with the same dimensions 176 | this(Q)(auto ref const Q qty) 177 | if (isQVariant!Q) 178 | { 179 | _value = qty._value; 180 | _dimensions = qty._dimensions; 181 | } 182 | 183 | /// ditto 184 | this(Q)(auto ref const Q qty) 185 | if (isQuantity!Q) 186 | { 187 | this = qty.qVariant; 188 | } 189 | 190 | /// Creates a new dimensionless quantity from a number 191 | this(T)(T scalar) 192 | if (isNumeric!T) 193 | { 194 | _dimensions = Dimensions.init; 195 | _value = scalar; 196 | } 197 | 198 | /// Returns the dimensions of the quantity 199 | Dimensions dimensions() @property const 200 | { 201 | return _dimensions; 202 | } 203 | 204 | /++ 205 | Implicitly convert a dimensionless value to the value type. 206 | 207 | Calling get will throw DimensionException if the quantity is not 208 | dimensionless. 209 | +/ 210 | N get() const 211 | { 212 | checkDimensionless; 213 | return _value; 214 | } 215 | 216 | alias get this; 217 | 218 | /++ 219 | Gets the _value of this quantity when expressed in the given target unit. 220 | +/ 221 | N value(Q)(auto ref const Q target) const 222 | if (isQVariantOrQuantity!Q) 223 | { 224 | checkDim(target.dimensions); 225 | return _value / target.rawValue; 226 | } 227 | /// 228 | @safe pure unittest 229 | { 230 | auto minute = unit!int("T"); 231 | auto hour = 60 * minute; 232 | 233 | QVariant!int time = 120 * minute; 234 | assert(time.value(hour) == 2); 235 | assert(time.value(minute) == 120); 236 | } 237 | 238 | /++ 239 | Test whether this quantity is dimensionless 240 | +/ 241 | bool isDimensionless() @property const 242 | { 243 | return _dimensions.empty; 244 | } 245 | 246 | /++ 247 | Tests wheter this quantity has the same dimensions as another one. 248 | +/ 249 | bool isConsistentWith(Q)(auto ref const Q qty) const 250 | if (isQVariantOrQuantity!Q) 251 | { 252 | return _dimensions == qty.dimensions; 253 | } 254 | /// 255 | @safe pure unittest 256 | { 257 | auto second = unit!double("T"); 258 | auto minute = 60 * second; 259 | auto meter = unit!double("L"); 260 | 261 | assert(minute.isConsistentWith(second)); 262 | assert(!meter.isConsistentWith(second)); 263 | } 264 | 265 | /++ 266 | Returns the base unit of this quantity. 267 | +/ 268 | QVariant baseUnit() @property const 269 | { 270 | return QVariant(1, _dimensions); 271 | } 272 | 273 | /++ 274 | Cast a dimensionless quantity to a numeric type. 275 | 276 | The cast operation will throw DimensionException if the quantity is not 277 | dimensionless. 278 | +/ 279 | T opCast(T)() const 280 | if (isNumeric!T) 281 | { 282 | checkDimensionless; 283 | return _value; 284 | } 285 | 286 | // Assign from another quantity 287 | /// Operator overloading 288 | ref QVariant opAssign(Q)(auto ref const Q qty) 289 | if (isQVariantOrQuantity!Q) 290 | { 291 | _dimensions = qty.dimensions; 292 | _value = qty.rawValue; 293 | return this; 294 | } 295 | 296 | // Assign from a numeric value if this quantity is dimensionless 297 | /// ditto 298 | ref QVariant opAssign(T)(T scalar) 299 | if (isNumeric!T) 300 | { 301 | _dimensions = Dimensions.init; 302 | _value = scalar; 303 | return this; 304 | } 305 | 306 | // Unary + and - 307 | /// ditto 308 | QVariant!N opUnary(string op)() const 309 | if (op == "+" || op == "-") 310 | { 311 | return QVariant(mixin(op ~ "_value"), _dimensions); 312 | } 313 | 314 | // Unary ++ and -- 315 | /// ditto 316 | QVariant!N opUnary(string op)() 317 | if (op == "++" || op == "--") 318 | { 319 | mixin(op ~ "_value;"); 320 | return this; 321 | } 322 | 323 | // Add (or substract) two quantities if they share the same dimensions 324 | /// ditto 325 | QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 326 | if (isQVariantOrQuantity!Q && (op == "+" || op == "-")) 327 | { 328 | checkDim(qty.dimensions); 329 | return QVariant(mixin("_value" ~ op ~ "qty.rawValue"), _dimensions); 330 | } 331 | 332 | /// ditto 333 | QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 334 | if (isQVariantOrQuantity!Q && (op == "+" || op == "-")) 335 | { 336 | checkDim(qty.dimensions); 337 | return QVariant(mixin("qty.rawValue" ~ op ~ "_value"), _dimensions); 338 | } 339 | 340 | // Add (or substract) a dimensionless quantity and a number 341 | /// ditto 342 | QVariant!N opBinary(string op, T)(T scalar) const 343 | if (isNumeric!T && (op == "+" || op == "-")) 344 | { 345 | checkDimensionless; 346 | return QVariant(mixin("_value" ~ op ~ "scalar"), _dimensions); 347 | } 348 | 349 | /// ditto 350 | QVariant!N opBinaryRight(string op, T)(T scalar) const 351 | if (isNumeric!T && (op == "+" || op == "-")) 352 | { 353 | checkDimensionless; 354 | return QVariant(mixin("scalar" ~ op ~ "_value"), _dimensions); 355 | } 356 | 357 | // Multiply or divide a quantity by a number 358 | /// ditto 359 | QVariant!N opBinary(string op, T)(T scalar) const 360 | if (isNumeric!T && (op == "*" || op == "/" || op == "%")) 361 | { 362 | return QVariant(mixin("_value" ~ op ~ "scalar"), _dimensions); 363 | } 364 | 365 | /// ditto 366 | QVariant!N opBinaryRight(string op, T)(T scalar) const 367 | if (isNumeric!T && op == "*") 368 | { 369 | return QVariant(mixin("scalar" ~ op ~ "_value"), _dimensions); 370 | } 371 | 372 | /// ditto 373 | QVariant!N opBinaryRight(string op, T)(T scalar) const 374 | if (isNumeric!T && (op == "/" || op == "%")) 375 | { 376 | return QVariant(mixin("scalar" ~ op ~ "_value"), ~_dimensions); 377 | } 378 | 379 | // Multiply or divide two quantities 380 | /// ditto 381 | QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 382 | if (isQVariantOrQuantity!Q && (op == "*" || op == "/")) 383 | { 384 | return QVariant(mixin("_value" ~ op ~ "qty.rawValue"), 385 | mixin("_dimensions" ~ op ~ "qty.dimensions")); 386 | } 387 | 388 | /// ditto 389 | QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 390 | if (isQVariantOrQuantity!Q && (op == "*" || op == "/")) 391 | { 392 | return QVariant(mixin("qty.rawValue" ~ op ~ "_value"), 393 | mixin("qty.dimensions" ~ op ~ "_dimensions")); 394 | } 395 | 396 | /// ditto 397 | QVariant!N opBinary(string op, Q)(auto ref const Q qty) const 398 | if (isQVariantOrQuantity!Q && (op == "%")) 399 | { 400 | checkDim(qty.dimensions); 401 | return QVariant(_value % qty.rawValue, _dimensions); 402 | } 403 | 404 | /// ditto 405 | QVariant!N opBinaryRight(string op, Q)(auto ref const Q qty) const 406 | if (isQVariantOrQuantity!Q && (op == "%")) 407 | { 408 | checkDim(qty.dimensions); 409 | return QVariant(qty.rawValue % _value, _dimensions); 410 | } 411 | 412 | /// ditto 413 | QVariant!N opBinary(string op, T)(T power) const 414 | if (isIntegral!T && op == "^^") 415 | { 416 | return QVariant(_value ^^ power, _dimensions.pow(Rational(power))); 417 | } 418 | 419 | /// ditto 420 | QVariant!N opBinary(string op)(Rational power) const 421 | if (op == "^^") 422 | { 423 | static if (isIntegral!N) 424 | auto newValue = std.math.pow(_value, cast(real) power).roundTo!N; 425 | else static if (isFloatingPoint!N) 426 | auto newValue = std.math.pow(_value, cast(real) power); 427 | else 428 | static assert(false, "Operation not defined for " ~ QVariant!N.stringof); 429 | return QVariant(newValue, _dimensions.pow(power)); 430 | } 431 | 432 | // Add/sub assign with a quantity that shares the same dimensions 433 | /// ditto 434 | void opOpAssign(string op, Q)(auto ref const Q qty) 435 | if (isQVariantOrQuantity!Q && (op == "+" || op == "-")) 436 | { 437 | checkDim(qty.dimensions); 438 | mixin("_value " ~ op ~ "= qty.rawValue;"); 439 | } 440 | 441 | // Add/sub assign a number to a dimensionless quantity 442 | /// ditto 443 | void opOpAssign(string op, T)(T scalar) 444 | if (isNumeric!T && (op == "+" || op == "-")) 445 | { 446 | checkDimensionless; 447 | mixin("_value " ~ op ~ "= scalar;"); 448 | } 449 | 450 | // Mul/div assign another quantity to a quantity 451 | /// ditto 452 | void opOpAssign(string op, Q)(auto ref const Q qty) 453 | if (isQVariantOrQuantity!Q && (op == "*" || op == "/" || op == "%")) 454 | { 455 | mixin("_value" ~ op ~ "= qty.rawValue;"); 456 | static if (op == "*") 457 | _dimensions = _dimensions * qty.dimensions; 458 | else 459 | _dimensions = _dimensions / qty.dimensions; 460 | } 461 | 462 | // Mul/div assign a number to a quantity 463 | /// ditto 464 | void opOpAssign(string op, T)(T scalar) 465 | if (isNumeric!T && (op == "*" || op == "/")) 466 | { 467 | mixin("_value" ~ op ~ "= scalar;"); 468 | } 469 | 470 | /// ditto 471 | void opOpAssign(string op, T)(T scalar) 472 | if (isNumeric!T && op == "%") 473 | { 474 | checkDimensionless; 475 | mixin("_value" ~ op ~ "= scalar;"); 476 | } 477 | 478 | // Exact equality between quantities 479 | /// ditto 480 | bool opEquals(Q)(auto ref const Q qty) const 481 | if (isQVariantOrQuantity!Q) 482 | { 483 | checkDim(qty.dimensions); 484 | return _value == qty.rawValue; 485 | } 486 | 487 | // Exact equality between a dimensionless quantity and a number 488 | /// ditto 489 | bool opEquals(T)(T scalar) const 490 | if (isNumeric!T) 491 | { 492 | checkDimensionless; 493 | return _value == scalar; 494 | } 495 | 496 | // Comparison between two quantities 497 | /// ditto 498 | int opCmp(Q)(auto ref const Q qty) const 499 | if (isQVariantOrQuantity!Q) 500 | { 501 | checkDim(qty.dimensions); 502 | if (_value == qty.rawValue) 503 | return 0; 504 | if (_value < qty.rawValue) 505 | return -1; 506 | return 1; 507 | } 508 | 509 | // Comparison between a dimensionless quantity and a number 510 | /// ditto 511 | int opCmp(T)(T scalar) const 512 | if (isNumeric!T) 513 | { 514 | checkDimensionless; 515 | if (_value < scalar) 516 | return -1; 517 | if (_value > scalar) 518 | return 1; 519 | return 0; 520 | } 521 | 522 | void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const 523 | { 524 | sink.formatValue(_value, fmt); 525 | sink(" "); 526 | sink.formattedWrite!"%s"(_dimensions); 527 | } 528 | } 529 | 530 | /++ 531 | Creates a new monodimensional unit as a QVariant. 532 | 533 | Params: 534 | N = The numeric type of the value part of the quantity. 535 | 536 | dimSymbol = The symbol of the dimension of this quantity. 537 | 538 | rank = The rank of the dimensions of this quantity in the dimension vector, 539 | when combining this quantity with other oned. 540 | +/ 541 | QVariant!N unit(N)(string dimSymbol, size_t rank = size_t.max) 542 | { 543 | return QVariant!N(N(1), Dimensions.mono(dimSymbol, rank)); 544 | } 545 | /// 546 | unittest 547 | { 548 | enum meter = unit!double("L", 1); 549 | enum kilogram = unit!double("M", 2); 550 | // Dimensions will be in this order: L M 551 | } 552 | 553 | // Tests whether T is a quantity type 554 | template isQVariant(T) 555 | { 556 | alias U = Unqual!T; 557 | static if (is(U == QVariant!X, X...)) 558 | enum isQVariant = true; 559 | else 560 | enum isQVariant = false; 561 | } 562 | 563 | enum isQVariantOrQuantity(T) = isQVariant!T || isQuantity!T; 564 | 565 | /// Turns a Quantity into a QVariant 566 | auto qVariant(Q)(auto ref const Q qty) 567 | if (isQuantity!Q) 568 | { 569 | return QVariant!(Q.valueType)(qty.rawValue, qty.dimensions); 570 | } 571 | 572 | /// Turns a scalar into a dimensionless QVariant 573 | auto qVariant(N)(N scalar) 574 | if (isNumeric!N) 575 | { 576 | return QVariant!N(scalar, Dimensions.init); 577 | } 578 | 579 | /// Basic math functions that work with QVariant. 580 | auto square(Q)(auto ref const Q quantity) 581 | if (isQVariant!Q) 582 | { 583 | return Q(quantity._value ^^ 2, quantity._dimensions.pow(2)); 584 | } 585 | 586 | /// ditto 587 | auto sqrt(Q)(auto ref const Q quantity) 588 | if (isQVariant!Q) 589 | { 590 | return Q(std.math.sqrt(quantity._value), quantity._dimensions.powinverse(2)); 591 | } 592 | 593 | /// ditto 594 | auto cubic(Q)(auto ref const Q quantity) 595 | if (isQVariant!Q) 596 | { 597 | return Q(quantity._value ^^ 3, quantity._dimensions.pow(3)); 598 | } 599 | 600 | /// ditto 601 | auto cbrt(Q)(auto ref const Q quantity) 602 | if (isQVariant!Q) 603 | { 604 | return Q(std.math.cbrt(quantity._value), quantity._dimensions.powinverse(3)); 605 | } 606 | 607 | /// ditto 608 | auto pow(Q)(auto ref const Q quantity, Rational r) 609 | if (isQVariant!Q) 610 | { 611 | return quantity ^^ r; 612 | } 613 | 614 | auto pow(Q, I)(auto ref const Q quantity, I n) 615 | if (isQVariant!Q && isIntegral!I) 616 | { 617 | return quantity ^^ Rational(n); 618 | } 619 | 620 | /// ditto 621 | auto nthRoot(Q)(auto ref const Q quantity, Rational r) 622 | if (isQVariant!Q) 623 | { 624 | return quantity ^^ r.inverted; 625 | } 626 | 627 | auto nthRoot(Q, I)(auto ref const Q quantity, I n) 628 | if (isQVariant!Q && isIntegral!I) 629 | { 630 | return nthRoot(quantity, Rational(n)); 631 | } 632 | 633 | /// ditto 634 | Q abs(Q)(auto ref const Q quantity) 635 | if (isQVariant!Q) 636 | { 637 | return Q(std.math.fabs(quantity._value), quantity._dimensions); 638 | } 639 | -------------------------------------------------------------------------------- /source/quantities/si.d: -------------------------------------------------------------------------------- 1 | /++ 2 | Importing `quantities.si` instantiate all the definitions of the SI units, 3 | prefixes, parsing functions and formatting functions, both at run-time and 4 | compile-time, storint their values as a `double`. 5 | 6 | Copyright: Copyright 2013-2018, Nicolas Sicard 7 | Authors: Nicolas Sicard 8 | License: $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Standards: $(LINK http://www.bipm.org/en/si/si_brochure/) 10 | Source: $(LINK https://github.com/biozic/quantities) 11 | +/ 12 | module quantities.si; 13 | 14 | import quantities.internal.si; 15 | 16 | mixin SIDefinitions!double; 17 | -------------------------------------------------------------------------------- /tests/fake_units_tests.d: -------------------------------------------------------------------------------- 1 | module tests.fake_units_tests; 2 | 3 | debug import std.stdio; 4 | 5 | @("Let there be units") 6 | unittest 7 | { 8 | import quantities; 9 | import std.exception : assertThrown; 10 | 11 | // Let there be units 12 | auto apple = unit!int("Apple"); 13 | auto cookie = unit!int("Cookie"); 14 | auto movie = unit!int("Movie"); 15 | 16 | // Let there be prefixes 17 | alias few = prefix!2; 18 | alias many = prefix!100; 19 | 20 | auto tolerated = few(cookie) / movie; 21 | auto toxic = 50 * tolerated; 22 | 23 | // 100 cookies is really too much for one movie 24 | assert(toxic.value(cookie / movie) == 100); 25 | 26 | // How many cookies are tolerated if I watch 10 movies a week 27 | assert((tolerated * 10 * movie).value(cookie) == 20); 28 | 29 | // Don't mix cookies with apples 30 | assertThrown!DimensionException(cookie + apple); 31 | 32 | // Let there be a parser 33 | auto symbols = SymbolList!int().addUnit("🍎", apple).addUnit("🍪", 34 | cookie).addUnit("🎬", movie).addPrefix("🙂", 2).addPrefix("😃", 100); 35 | auto parser = Parser!int(symbols); 36 | 37 | // Use parsed quantities 38 | assert(tolerated == parser.parse("🙂🍪/🎬")); 39 | assert(toxic == parser.parse("😃🍪/🎬")); 40 | } 41 | -------------------------------------------------------------------------------- /tests/more_tests.d: -------------------------------------------------------------------------------- 1 | module tests.more_tests; 2 | 3 | import quantities; 4 | import std.math : approxEqual; 5 | 6 | @("qVariant") 7 | unittest 8 | { 9 | assert(is(typeof(meter.qVariant) == QVariant!double)); 10 | assert(meter == unit!double("L", 1)); 11 | assert(QVariant!double(meter) == unit!double("L", 1)); 12 | } 13 | 14 | @("QVariant/Quantity") 15 | @safe pure unittest 16 | { 17 | enum meterVariant = meter.qVariant; 18 | assert(meterVariant.value(meter) == 1); 19 | assert(meterVariant.isConsistentWith(meter)); 20 | meterVariant = meter + (meter * meter) / meter; 21 | assert(meterVariant.value(meter) == 1); 22 | meterVariant += meter; 23 | meterVariant -= meter; 24 | assert(meterVariant.value(meter) == 1); 25 | meterVariant *= meter; 26 | meterVariant /= meter; 27 | assert(meterVariant.value(meter) == 1); 28 | assert(meterVariant == meter); 29 | assert(meterVariant < 2 * meter); 30 | 31 | immutable secondVariant = (2 * second).qVariant; 32 | auto test = secondVariant + second; 33 | assert(test.value(second).approxEqual(3)); 34 | test = second + secondVariant; 35 | assert(test.value(second).approxEqual(3)); 36 | test = secondVariant - second; 37 | assert(test.value(second).approxEqual(1)); 38 | test = second - secondVariant; 39 | assert(test.value(second).approxEqual(-1)); 40 | test = second * secondVariant; 41 | assert(test.value(square(second)).approxEqual(2)); 42 | test = secondVariant * second; 43 | assert(test.value(square(second)).approxEqual(2)); 44 | test = second / secondVariant; 45 | assert(test.value(one).approxEqual(0.5)); 46 | test = secondVariant / second; 47 | assert(test.value(one).approxEqual(2)); 48 | test = secondVariant % second; 49 | assert(test.value(second).approxEqual(0)); 50 | test = (4 * second) % secondVariant; 51 | assert(test.value(second).approxEqual(0)); 52 | } 53 | 54 | @("Quantity/QVariant") 55 | unittest 56 | { 57 | Time duration = Time(parseSI("60 min")); 58 | assert(duration.value(parseSI("min")) == 60); 59 | duration = parseSI("60 s"); 60 | assert(duration.value(parseSI("s")) == 60); 61 | assert(duration.isConsistentWith(parseSI("1 h"))); 62 | } 63 | 64 | @("Functions with QVariant parameters") 65 | unittest 66 | { 67 | static QVariant!double catc(QVariant!double deltaAbs, QVariant!double deltaTime = 1 * minute) 68 | out (result) 69 | { 70 | assert(result.isConsistentWith(katal / liter)); 71 | } 72 | do 73 | { 74 | immutable epsilon = 6.3 * liter / milli(mole) / centi(meter); 75 | immutable totalVolume = 285 * micro(liter); 76 | immutable sampleVolume = 25 * micro(liter); 77 | immutable lightPath = 0.6 * centi(meter); 78 | return deltaAbs / deltaTime / (epsilon * lightPath) * totalVolume / sampleVolume; 79 | } 80 | 81 | assert(catc(0.031.qVariant).value(parseSI("µmol/min/L")).approxEqual(93.5)); 82 | } 83 | 84 | @("Functions with Quantity parameters") 85 | pure unittest 86 | { 87 | static auto catc(Dimensionless deltaAbs, Time deltaTime = 1 * minute) 88 | { 89 | enum epsilon = 6.3 * liter / milli(mole) / centi(meter); 90 | enum totalVolume = 285 * micro(liter); 91 | enum sampleVolume = 25 * micro(liter); 92 | enum lightPath = 0.6 * centi(meter); 93 | return deltaAbs / deltaTime / (epsilon * lightPath) * totalVolume / sampleVolume; 94 | } 95 | 96 | static assert(catc(0.031 * one).value(si!"µmol/min/L").approxEqual(93.5)); 97 | } 98 | 99 | @("toString") 100 | unittest 101 | { 102 | import std.conv : text; 103 | 104 | auto resistance = 4000 * ohm; 105 | assert(resistance.text == "4000 [L^2 M T^-3 I^-2]"); 106 | } 107 | 108 | @("siFormat RT") 109 | unittest 110 | { 111 | QVariant!double speed = 12.5 * kilo(meter) / hour; 112 | 113 | assert(siFormat("%.2f m/s", speed) == "3.47 m/s"); 114 | assert(siFormat("%.2f m/s"w, speed) == "3.47 m/s"w); 115 | assert(siFormat("%.2f m/s"d, speed) == "3.47 m/s"d); 116 | } 117 | -------------------------------------------------------------------------------- /tests/quantity_tests.d: -------------------------------------------------------------------------------- 1 | module tests.quantity_tests; 2 | 3 | import quantities; 4 | import quantities.internal.dimensions; 5 | import std.exception; 6 | import std.math : approxEqual; 7 | 8 | enum meter = unit!(double, "L"); 9 | enum second = unit!(double, "T"); 10 | enum radian = unit!(double, null); 11 | 12 | alias Length = typeof(meter); 13 | alias Time = typeof(second); 14 | alias Angle = typeof(radian); 15 | 16 | @("this()") 17 | @safe pure nothrow unittest 18 | { 19 | auto distance = Length(meter); 20 | auto angle = Angle(3.14); 21 | auto length = unit!double("L"); 22 | assert(length.dimensions == distance.dimensions); 23 | assert(!__traits(compiles, Length(2.0))); 24 | } 25 | 26 | @("get/alias this for dimensionless values") 27 | @safe pure nothrow unittest 28 | { 29 | double scalar = radian; 30 | assert(scalar == 1); 31 | } 32 | 33 | @("value(Q)") 34 | @safe pure nothrow unittest 35 | { 36 | auto distance = meter; 37 | assert(distance.value(meter) == 1); 38 | } 39 | 40 | @("isDimensionless") 41 | @safe pure nothrow unittest 42 | { 43 | assert(!meter.isDimensionless); 44 | assert(radian.isDimensionless); 45 | } 46 | 47 | @("isConsistentWith") 48 | @safe pure nothrow unittest 49 | { 50 | assert(meter.isConsistentWith(meter)); 51 | assert(!meter.isConsistentWith(second)); 52 | } 53 | 54 | @("opCast") 55 | @safe pure nothrow unittest 56 | { 57 | auto value = cast(double) radian; 58 | assert(!__traits(compiles, cast(double) meter)); 59 | } 60 | 61 | @("opAssign Q") 62 | @safe pure nothrow unittest 63 | { 64 | Length l1, l2; 65 | l1 = l2 = meter; 66 | 67 | assert(!__traits(compiles, l1 = second)); 68 | } 69 | 70 | @("opAssign T") 71 | @safe pure nothrow unittest 72 | { 73 | Angle angle; 74 | angle = 1; 75 | } 76 | 77 | @("opUnary + -") 78 | @safe pure nothrow unittest 79 | { 80 | auto plus = +meter; 81 | assert(plus.value(meter) == 1); 82 | auto minus = -meter; 83 | assert(minus.value(meter) == -1); 84 | } 85 | 86 | @("opUnary ++ --") 87 | @safe pure nothrow unittest 88 | { 89 | auto len = meter; 90 | ++len; 91 | assert(len.value(meter).approxEqual(2)); 92 | assert((len++).value(meter).approxEqual(2)); 93 | assert(len.value(meter).approxEqual(3)); 94 | --len; 95 | assert(len.value(meter).approxEqual(2)); 96 | assert((len--).value(meter).approxEqual(2)); 97 | assert(len.value(meter).approxEqual(1)); 98 | } 99 | 100 | @("opBinary Q+Q Q-Q") 101 | @safe pure nothrow unittest 102 | { 103 | auto plus = meter + meter; 104 | assert(plus.value(meter) == 2); 105 | auto minus = meter - meter; 106 | assert(minus.value(meter) == 0); 107 | 108 | assert(!__traits(compiles, meter + second)); 109 | assert(!__traits(compiles, meter - second)); 110 | } 111 | 112 | @("opBinary Q+N N+Q Q-N N-Q") 113 | @safe pure nothrow unittest 114 | { 115 | auto a1 = radian + 10; 116 | assert(a1.value(radian).approxEqual(11)); 117 | auto a2 = radian - 10; 118 | assert(a2.value(radian).approxEqual(-9)); 119 | 120 | auto a3 = 10 + radian; 121 | assert(a3.value(radian).approxEqual(11)); 122 | auto a4 = 10 - radian; 123 | assert(a4.value(radian).approxEqual(9)); 124 | 125 | assert(!__traits(compiles, meter + 1)); 126 | assert(!__traits(compiles, meter - 1)); 127 | assert(!__traits(compiles, 1 + meter)); 128 | assert(!__traits(compiles, 1 - meter)); 129 | } 130 | 131 | @("opBinary Q*N, N*Q, Q/N, N/Q, Q%N, N%Q") 132 | @safe pure nothrow unittest 133 | { 134 | auto m1 = meter * 10; 135 | assert(m1.value(meter).approxEqual(10)); 136 | auto m2 = 10 * meter; 137 | assert(m2.value(meter).approxEqual(10)); 138 | auto m3 = meter / 10; 139 | assert(m3.value(meter).approxEqual(0.1)); 140 | auto m4 = 10 / meter; 141 | assert(m4.dimensions == ~meter.dimensions); 142 | assert(m4.value(1 / meter).approxEqual(10)); 143 | auto m5 = m1 % 2; 144 | assert(m5.value(meter).approxEqual(0)); 145 | auto m6 = 10 % (2 * radian); 146 | assert(m6.value(radian).approxEqual(0)); 147 | } 148 | @("opBinary Q*Q, Q/Q, Q%Q") 149 | @safe pure unittest 150 | { 151 | auto surface = (10 * meter) * (10 * meter); 152 | assert(surface.value(meter * meter).approxEqual(100)); 153 | assert(surface.dimensions == meter.dimensions.pow(2)); 154 | 155 | auto speed = (10 * meter) / (5 * second); 156 | assert(speed.value(meter / second).approxEqual(2)); 157 | assert(speed.dimensions == meter.dimensions / second.dimensions); 158 | 159 | auto surfaceMod10 = surface % (10 * meter * meter); 160 | assert(surfaceMod10.value(meter * meter).approxEqual(0)); 161 | assert(surfaceMod10.dimensions == surface.dimensions); 162 | 163 | assert(!__traits(compiles, meter % second)); 164 | } 165 | @("opBinary Q^^I Q^^R") 166 | @safe pure nothrow unittest 167 | { 168 | // Operator ^^ is not available for Quantity 169 | } 170 | 171 | @("opOpAssign Q+=Q Q-=Q") 172 | @safe pure nothrow unittest 173 | { 174 | auto time = 10 * second; 175 | time += 50 * second; 176 | assert(time.value(second).approxEqual(60)); 177 | time -= 40 * second; 178 | assert(time.value(second).approxEqual(20)); 179 | } 180 | 181 | @("opOpAssign Q*=N Q/=N Q%=N") 182 | @safe pure nothrow unittest 183 | { 184 | auto time = 20 * second; 185 | time *= 2; 186 | assert(time.value(second).approxEqual(40)); 187 | time /= 4; 188 | assert(time.value(second).approxEqual(10)); 189 | 190 | auto angle = 2 * radian; 191 | angle += 4; 192 | assert(angle.value(radian).approxEqual(6)); 193 | angle -= 1; 194 | assert(angle.value(radian).approxEqual(5)); 195 | angle %= 2; 196 | assert(angle.value(radian).approxEqual(1)); 197 | 198 | assert(!__traits(compiles, time %= 3)); 199 | } 200 | 201 | @("opOpAssign Q*=Q Q/=Q Q%=Q") 202 | @safe pure nothrow unittest 203 | { 204 | auto angle = 50 * radian; 205 | angle *= 2 * radian; 206 | assert(angle.value(radian).approxEqual(100)); 207 | angle /= 2 * radian; 208 | assert(angle.value(radian).approxEqual(50)); 209 | angle %= 5 * radian; 210 | assert(angle.value(radian).approxEqual(0)); 211 | 212 | auto time = second; 213 | assert(!__traits(compiles, time *= second)); 214 | assert(!__traits(compiles, time /= second)); 215 | assert(!__traits(compiles, time %= second)); 216 | } 217 | 218 | @("opEquals Q==Q Q==N") 219 | @safe pure nothrow unittest 220 | { 221 | auto minute = 60 * second; 222 | assert(minute == 60 * second); 223 | assert(radian == 1); 224 | 225 | assert(!__traits(compiles, meter == second)); 226 | assert(!__traits(compiles, meter == 1)); 227 | } 228 | 229 | @("opCmp Q minute); 237 | assert(hour >= hour); 238 | 239 | assert(!__traits(compiles, second < meter)); 240 | } 241 | 242 | @("opCmp Q 1); 249 | assert(angle >= 2); 250 | 251 | assert(!__traits(compiles, meter < 1)); 252 | } 253 | 254 | @("toString") 255 | unittest 256 | { 257 | import std.conv : text; 258 | 259 | auto length = 12 * meter; 260 | assert(length.text == "12 [L]", length.text); 261 | } 262 | 263 | @("immutable") 264 | @safe pure nothrow unittest 265 | { 266 | immutable inch = 0.0254 * meter; 267 | immutable minute = 60 * second; 268 | immutable speed = inch / minute; 269 | } 270 | 271 | @("square/sqrt") 272 | @safe nothrow unittest 273 | { 274 | auto m2 = square(3 * meter); 275 | assert(m2.value(meter * meter).approxEqual(9)); 276 | auto m = sqrt(m2); 277 | assert(m.value(meter).approxEqual(3)); 278 | } 279 | 280 | @("cubic/cbrt") 281 | @safe nothrow unittest 282 | { 283 | auto m3 = cubic(2 * meter); 284 | assert(m3.value(meter * meter * meter).approxEqual(8)); 285 | 286 | // Doesn't work at compile time 287 | auto m = cbrt(m3); 288 | assert(m.value(meter).approxEqual(2)); 289 | } 290 | 291 | @("pow/nthRoot") 292 | @safe nothrow unittest 293 | { 294 | auto m5 = pow!5(2 * meter); 295 | assert(m5.value(meter * meter * meter * meter * meter).approxEqual(2 ^^ 5)); 296 | 297 | auto m = nthRoot!5(m5); 298 | assert(m.value(meter).approxEqual(2)); 299 | } 300 | 301 | @("abs") 302 | @safe nothrow unittest 303 | { 304 | assert(abs(-meter) == meter); 305 | } 306 | 307 | @("parseSI string") 308 | unittest 309 | { 310 | import quantities.si; 311 | 312 | auto resistance = parseSI("1000 m^2 kg s^-3 A^-2"); 313 | assert(resistance == 1000 * ohm); 314 | } 315 | 316 | @("parseSI wstring") 317 | unittest 318 | { 319 | import quantities.si; 320 | 321 | auto resistance = parseSI("1000 m^2 kg s^-3 A^-2"w); 322 | assert(resistance == 1000 * ohm); 323 | } 324 | 325 | @("parseSI dstring") 326 | unittest 327 | { 328 | import quantities.si; 329 | 330 | auto resistance = parseSI("1000 m^2 kg s^-3 A^-2"d); 331 | assert(resistance == 1000 * ohm); 332 | } 333 | -------------------------------------------------------------------------------- /tests/qvariant_tests.d: -------------------------------------------------------------------------------- 1 | module tests.qvariant_tests; 2 | 3 | import quantities; 4 | import quantities.internal.dimensions; 5 | import std.exception; 6 | import std.math : approxEqual; 7 | 8 | enum meter = unit!double("L"); 9 | enum second = unit!double("T"); 10 | enum radian = unit!double(null); 11 | 12 | void checkIncompatibleDimensions(E)(lazy E expression, QVariant!double lhs, QVariant!double rhs) 13 | { 14 | auto e = collectException!DimensionException(expression()); 15 | assert(e, "No DimensionException was thrown"); 16 | assert(e.thisDim == lhs.dimensions); 17 | assert(e.otherDim == rhs.dimensions); 18 | assert(e.msg == "Incompatible dimensions"); 19 | } 20 | 21 | void checkNotDimensionless(E)(lazy E expression, QVariant!double operand) 22 | { 23 | auto e = collectException!DimensionException(expression()); 24 | assert(e, "No DimensionException was thrown"); 25 | assert(e.thisDim == operand.dimensions); 26 | assert(e.otherDim == Dimensions.init); 27 | assert(e.msg == "Not dimensionless"); 28 | } 29 | 30 | @("this()") 31 | @safe pure nothrow unittest 32 | { 33 | auto distance = 2 * meter; 34 | auto angle = 3.14 * radian; 35 | // No actual test 36 | } 37 | 38 | @("get/alias this for dimensionless values") 39 | @safe pure unittest 40 | { 41 | double scalar = radian; 42 | assert(scalar == 1); 43 | } 44 | 45 | @("value(Q)") 46 | @safe pure unittest 47 | { 48 | auto distance = meter; 49 | assert(distance.value(meter) == 1); 50 | } 51 | 52 | @("isDimensionless") 53 | @safe pure nothrow unittest 54 | { 55 | assert(!meter.isDimensionless); 56 | assert(radian.isDimensionless); 57 | } 58 | 59 | @("isConsistentWith") 60 | @safe pure unittest 61 | { 62 | assert(meter.isConsistentWith(meter)); 63 | assert(!meter.isConsistentWith(second)); 64 | } 65 | 66 | @("opCast") 67 | @safe pure unittest 68 | { 69 | auto value = cast(double) radian; 70 | checkNotDimensionless(cast(double) meter, meter); 71 | } 72 | 73 | @("opAssign Q") 74 | @safe pure unittest 75 | { 76 | auto l1 = meter; 77 | // QVariant allows assignment to a quantity with different dimensions 78 | l1 = second; 79 | } 80 | 81 | @("opAssign T") 82 | @safe pure unittest 83 | { 84 | auto angle = 1; 85 | } 86 | 87 | @("opUnary + -") 88 | @safe pure unittest 89 | { 90 | auto plus = +meter; 91 | assert(plus.value(meter) == 1); 92 | auto minus = -meter; 93 | assert(minus.value(meter) == -1); 94 | } 95 | 96 | @("opUnary ++ --") 97 | @safe pure unittest 98 | { 99 | auto len = meter; 100 | ++len; 101 | assert(len.value(meter).approxEqual(2)); 102 | assert((len++).value(meter).approxEqual(2)); 103 | assert(len.value(meter).approxEqual(3)); 104 | --len; 105 | assert(len.value(meter).approxEqual(2)); 106 | assert((len--).value(meter).approxEqual(2)); 107 | assert(len.value(meter).approxEqual(1)); 108 | } 109 | 110 | @("opBinary Q+Q Q-Q") 111 | @safe pure unittest 112 | { 113 | auto plus = meter + meter; 114 | assert(plus.value(meter) == 2); 115 | auto minus = meter - meter; 116 | assert(minus.value(meter) == 0); 117 | checkIncompatibleDimensions(meter + second, meter, second); 118 | checkIncompatibleDimensions(meter - second, meter, second); 119 | } 120 | 121 | @("opBinary Q+N N+Q Q-N N-Q") 122 | @safe pure unittest 123 | { 124 | auto a1 = radian + 10; 125 | assert(a1.value(radian).approxEqual(11)); 126 | auto a2 = radian - 10; 127 | assert(a2.value(radian).approxEqual(-9)); 128 | 129 | auto a3 = 10 + radian; 130 | assert(a3.value(radian).approxEqual(11)); 131 | auto a4 = 10 - radian; 132 | assert(a4.value(radian).approxEqual(9)); 133 | 134 | checkNotDimensionless(meter + 1, meter); 135 | checkNotDimensionless(meter - 1, meter); 136 | checkNotDimensionless(1 + meter, meter); 137 | checkNotDimensionless(1 - meter, meter); 138 | } 139 | 140 | @("opBinary Q*N, N*Q, Q/N, N/Q, Q%N, N%Q") 141 | @safe pure unittest 142 | { 143 | auto m1 = meter * 10; 144 | assert(m1.value(meter).approxEqual(10)); 145 | auto m2 = 10 * meter; 146 | assert(m2.value(meter).approxEqual(10)); 147 | auto m3 = meter / 10; 148 | assert(m3.value(meter).approxEqual(0.1)); 149 | auto m4 = 10 / meter; 150 | assert(m4.dimensions == ~meter.dimensions); 151 | assert(m4.value(1 / meter).approxEqual(10)); 152 | auto m5 = m1 % 2; 153 | assert(m5.value(meter).approxEqual(0)); 154 | auto m6 = 10 % (2 * radian); 155 | assert(m6.value(radian).approxEqual(0)); 156 | } 157 | 158 | @("opBinary Q*Q, Q/Q, Q%Q") 159 | @safe pure unittest 160 | { 161 | auto surface = (10 * meter) * (10 * meter); 162 | assert(surface.value(meter * meter).approxEqual(100)); 163 | assert(surface.dimensions == meter.dimensions.pow(2)); 164 | 165 | auto speed = (10 * meter) / (5 * second); 166 | assert(speed.value(meter / second).approxEqual(2)); 167 | assert(speed.dimensions == meter.dimensions / second.dimensions); 168 | 169 | auto surfaceMod10 = surface % (10 * meter * meter); 170 | assert(surfaceMod10.value(meter * meter).approxEqual(0)); 171 | assert(surfaceMod10.dimensions == surface.dimensions); 172 | 173 | checkIncompatibleDimensions(meter % second, meter, second); 174 | } 175 | 176 | @("opBinary Q^^I Q^^R") 177 | @safe pure unittest 178 | { 179 | auto x = 2 * meter; 180 | assert((x ^^ 3).value(meter * meter * meter).approxEqual(8)); 181 | assert((x ^^ Rational(3)).value(meter * meter * meter).approxEqual(8)); 182 | } 183 | 184 | @("opOpAssign Q+=Q Q-=Q") 185 | @safe pure unittest 186 | { 187 | auto time = 10 * second; 188 | time += 50 * second; 189 | assert(time.value(second).approxEqual(60)); 190 | time -= 40 * second; 191 | assert(time.value(second).approxEqual(20)); 192 | } 193 | 194 | @("opOpAssign Q*=N Q/=N Q%=N") 195 | @safe pure unittest 196 | { 197 | auto time = 20 * second; 198 | time *= 2; 199 | assert(time.value(second).approxEqual(40)); 200 | time /= 4; 201 | assert(time.value(second).approxEqual(10)); 202 | 203 | auto angle = 2 * radian; 204 | angle += 4; 205 | assert(angle.value(radian).approxEqual(6)); 206 | angle -= 1; 207 | assert(angle.value(radian).approxEqual(5)); 208 | angle %= 2; 209 | assert(angle.value(radian).approxEqual(1)); 210 | 211 | checkNotDimensionless(time %= 3, time); 212 | } 213 | 214 | @("opOpAssign Q*=Q Q/=Q Q%=Q") 215 | @safe pure unittest 216 | { 217 | auto angle = 2 * radian; 218 | angle *= 2 * radian; 219 | assert(angle.value(radian).approxEqual(4)); 220 | angle /= 2 * radian; 221 | assert(angle.value(radian).approxEqual(2)); 222 | 223 | auto qty = 100 * meter; 224 | qty *= second; 225 | qty /= 20 * second; 226 | qty %= 5 * second; 227 | assert(qty.value(meter / second).approxEqual(0)); 228 | } 229 | 230 | @("opEquals Q==Q Q==N") 231 | @safe pure unittest 232 | { 233 | auto minute = 60 * second; 234 | assert(minute == 60 * second); 235 | assert(radian == 1); 236 | 237 | checkIncompatibleDimensions(meter == second, meter, second); 238 | checkNotDimensionless(meter == 1, meter); 239 | } 240 | 241 | @("opCmp Q minute); 249 | assert(hour >= hour); 250 | 251 | checkIncompatibleDimensions(meter < second, meter, second); 252 | } 253 | 254 | @("opCmp Q 1); 261 | assert(angle >= 2); 262 | 263 | checkNotDimensionless(meter < 1, meter); 264 | } 265 | 266 | @("toString") 267 | unittest 268 | { 269 | import std.conv : text; 270 | 271 | auto length = 12 * meter; 272 | assert(length.text == "12 [L]", length.text); 273 | } 274 | 275 | @("immutable") 276 | @safe pure unittest 277 | { 278 | immutable inch = 0.0254 * meter; 279 | immutable minute = 60 * second; 280 | immutable speed = inch / minute; 281 | } 282 | 283 | @("square/sqrt") 284 | @safe unittest 285 | { 286 | auto m2 = square(3 * meter); 287 | assert(m2.value(meter * meter).approxEqual(9)); 288 | auto m = sqrt(m2); 289 | assert(m.value(meter).approxEqual(3)); 290 | } 291 | 292 | @("cubic/cbrt") 293 | @safe unittest 294 | { 295 | auto m3 = cubic(2 * meter); 296 | assert(m3.value(meter * meter * meter).approxEqual(8)); 297 | 298 | // Doesn't work at compile time 299 | auto m = cbrt(m3); 300 | assert(m.value(meter).approxEqual(2)); 301 | } 302 | 303 | @("pow/nthRoot") 304 | @safe unittest 305 | { 306 | auto m5 = pow(2 * meter, 5); 307 | assert(m5.value(meter * meter * meter * meter * meter).approxEqual(2 ^^ 5)); 308 | 309 | auto m = nthRoot(m5, 5); 310 | assert(m.value(meter).approxEqual(2)); 311 | } 312 | 313 | @("abs") 314 | @safe unittest 315 | { 316 | assert(abs(-meter) == meter); 317 | } 318 | 319 | @("parseSI string") 320 | unittest 321 | { 322 | import quantities.si; 323 | 324 | auto resistance = parseSI("1000 m^2 kg s^-3 A^-2"); 325 | assert(resistance == 1000 * ohm); 326 | } 327 | 328 | @("parseSI wstring") 329 | unittest 330 | { 331 | import quantities.si; 332 | 333 | auto resistance = parseSI("1000 m^2 kg s^-3 A^-2"w); 334 | assert(resistance == 1000 * ohm); 335 | } 336 | 337 | @("parseSI dstring") 338 | unittest 339 | { 340 | import quantities.si; 341 | 342 | auto resistance = parseSI("1000 m^2 kg s^-3 A^-2"d); 343 | assert(resistance == 1000 * ohm); 344 | } 345 | --------------------------------------------------------------------------------