├── .gitignore ├── LICENSE ├── README.md ├── metric.nimble ├── src ├── metric.nim └── metric │ ├── constants.nim │ ├── dimension.nim │ ├── natural.nim │ ├── siunits.nim │ ├── stddim.nim │ └── unit.nim └── tests └── tall.nim /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache 2 | tall 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael Jendrusch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # metric - bringing SI to Nim 2 | 3 | [![nimble](https://raw.githubusercontent.com/yglukhov/nimble-tag/master/nimble_js.png)](https://github.com/yglukhov/nimble-tag) 4 | 5 | `metric` is a small library providing type-level dimensional analysis. 6 | It allows you to keep track of the physical units of your programs, and can 7 | be useful for writing scientific software. 8 | 9 | ## Installation 10 | 11 | `metric` has no non-standard dependencies. Just type `nimble install metric` 12 | and you're good to go. 13 | 14 | ## A small usage example 15 | 16 | ```nim 17 | import metric, strformat 18 | 19 | ## Let's open a block with a lot of miscellaneous units predefinded: 20 | withUnits: 21 | var 22 | v0 = 40.0 * mile / hour # Some non-SI units of velocity. 23 | echo "v0 in miles per hour: ", fmt"{v0 as mile / hour} [mph]" 24 | echo "v0 in decimeters per fortnight: ", 25 | fmt"{v0 as dm / (2.0 * week)} [dm / fortnight]" 26 | echo "v0 in SI units: ", v0 27 | ``` 28 | 29 | As you can see, `metric` makes it easy to handle units, and never have to 30 | worry about keeping track of strange conversions. 31 | 32 | ## Custom dimensions 33 | 34 | `metric` provides the means to handle all dimensionful quantities under the 35 | SI system. Dimensions taken into consideration are: 36 | 37 | * `Length` 38 | * `Time` 39 | * `Mass` 40 | * `Amount` 41 | * `Temperature` 42 | * `Current` 43 | * `Intensity` 44 | 45 | Should you need different units, you can do that, by just defining them as 46 | `object of BaseDimension`, with the following procs and constants: 47 | 48 | ```nim 49 | type 50 | MyDimension = object of BaseDimension 51 | 52 | proc `$`(x: typedesc[MyDimension]): string = "mydim" ## \ 53 | ## Define how you want your dimension's base unit to be pretty-printed. 54 | 55 | const 56 | myUnitValue = Unit[MyDimension](val: 1.0) ## The name does not matter here. 57 | ``` 58 | 59 | With those things defined, you are good to use your units in the same way, as 60 | the built-ins. 61 | -------------------------------------------------------------------------------- /metric.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Michael Jendrusch" 5 | description = "Unit types for Nim." 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | # Dependencies 10 | 11 | requires "nim >= 0.17.3" 12 | 13 | proc testConfig() = 14 | --path: "../src" 15 | --run 16 | 17 | task test, "run metric tests": 18 | testConfig() 19 | setCommand "c", "tests/tall.nim" 20 | -------------------------------------------------------------------------------- /src/metric.nim: -------------------------------------------------------------------------------- 1 | # 2 | # metric - bringing the SI to Nim. 3 | # 4 | # (c) Copyright 2018 Michael Jendrusch. All rights reserved. 5 | # This library is licensed under the MIT license. 6 | # For more information see LICENSE. 7 | 8 | import metric / [dimension, unit, siunits, stddim] 9 | export dimension, unit, siunits, stddim 10 | -------------------------------------------------------------------------------- /src/metric/constants.nim: -------------------------------------------------------------------------------- 1 | # 2 | # metric - bringing the SI to Nim. 3 | # 4 | # (c) Copyright 2018 Michael Jendrusch. All rights reserved. 5 | # This library is licensed under the MIT license. 6 | # For more information see LICENSE. 7 | 8 | ## This module provides common physical constants with units. 9 | 10 | import math 11 | import dimension, unit, stddim, siunits 12 | 13 | const 14 | speedOfLight* = 299_792_458.0 * meter / second 15 | constantOfGravitation* = 6.674_08e-11 * meter ^ 3 / (kilogram * second ^ 2) 16 | reducedPlanckConstant* = 1.054_571_800e-34 * joule * second 17 | 18 | vacuumPermeability* = (4 * Pi) * 1.0e-7 * newton / ampere ^ 2 19 | vacuumPermittivity* = 8.854_187_817e-12 * farad / meter 20 | vacuumImpedance* = vacuumPermeability * speedOfLight 21 | elementaryCharge* = 1.602_176_621e-19 * coulomb 22 | electronMass* = 9.109_383_56e-31 * kilogram 23 | protonMass* = 1.672_621_898e-27 * kilogram 24 | fineStructureConstant* = elementaryCharge ^ 2 / 25 | (4.0 * Pi * vacuumPermittivity * 26 | reducedPlanckConstant) 27 | rydbergConstant* = fineStructureConstant ^ 2 * electronMass * speedOfLight / 28 | (4.0 * Pi * reducedPlanckConstant) 29 | 30 | coulombsConstant* = 1.0 / (4.0 * Pi * vacuumPermittivity) 31 | bohrMagneton* = elementaryCharge * reducedPlanckConstant / (2.0 * electronMass) 32 | conductanceQuantum* = elementaryCharge ^ 2 * 4.0 * Pi / 33 | reducedPlanckConstant 34 | josephsonConstant* = 4.0 * Pi * elementaryCharge / reducedPlanckConstant 35 | magneticFluxQuantum* = reducedPlanckConstant / (4.0 * Pi * elementaryCharge) 36 | 37 | atomicMass* = 1.660_539_040e-27 * kilogram 38 | avogadroConstant* = 6.022_140_857e23 * one / mole 39 | boltzmannConstant* = 1.380_648_52e-23 * joule / kelvin 40 | gasConstant* = 8.314_459_8 * joule / (mole * kelvin) 41 | 42 | template withPhysicalConstants*(body: untyped): untyped {. dirty .} = 43 | ## Creates a scoped environment in which shorthands for physical constants 44 | ## may be used. 45 | block: 46 | const 47 | c0 = speedOfLight 48 | G = constantOfGravitation 49 | hbar = reducedPlanckConstant 50 | mu0 = vacuumPermeability 51 | epsilon0 = vacuumPermittivity 52 | Z0 = vacuumImpedance 53 | e = elementaryCharge 54 | me = electronMass 55 | mp = protonMass 56 | alpha = fineStructureConstant 57 | RInfty = rydbergConstant 58 | ke = coulombsConstant 59 | muB = bohrMagneton 60 | G0 = conductanceQuantum 61 | KJ = josephsonConstant 62 | phi0 = magneticFluxQuantum 63 | unit = atomicMass 64 | u = atomicMass 65 | Da = atomicMass 66 | NA = avogardoConstant 67 | kB = boltzmannConstant 68 | R = gasConstant 69 | body 70 | -------------------------------------------------------------------------------- /src/metric/dimension.nim: -------------------------------------------------------------------------------- 1 | # 2 | # metric - bringing the SI to Nim. 3 | # 4 | # (c) Copyright 2018 Michael Jendrusch. All rights reserved. 5 | # This library is licensed under the MIT license. 6 | # For more information see LICENSE. 7 | 8 | import macros 9 | import algorithm, sequtils, tables 10 | 11 | type 12 | BaseDimension* = object {. pure, inheritable .} ## \ 13 | ## The basic dimension object all dimensions should inherit from. 14 | SomeDimension* = concept type Dim 15 | ## A concept ensuring a matched type inherits from ``BaseDimension``. 16 | var 17 | x: ptr Dim 18 | y: ptr BaseDimension 19 | y = x 20 | ProductDimension*[X: tuple] = object of BaseDimension ## \ 21 | ## A type encoding a product of dimensions inside a static parameter. 22 | ## The designated type for ``X`` may be ``static[auto]``, but is actually 23 | ## a static tuple of arbitrary length. ``static[auto]`` seems to be the 24 | ## only way of actually matching this. 25 | QuotientDimension*[X, Y] = object of BaseDimension ## \ 26 | ## A type encoding a quotient of two dimensions in its type parameters. 27 | DimensionLess* = object of BaseDimension ## \ 28 | ## A special dimension type encoding the lack of dimensionality, that is 29 | ## [1] in units. 30 | 31 | proc mulImpl(x, y: NimNode): NimNode 32 | proc divImpl(x, y: NimNode): NimNode 33 | proc powImpl(x: NimNode; y: int): NimNode 34 | 35 | proc expandTypeInstImpl(node: NimNode): NimNode = 36 | ## Recursively expands symbol nodes using ``getTypeInst``. 37 | if node.kind == nnkSym: 38 | let 39 | typeInst = node.getTypeInst 40 | if typeInst.len > 1: 41 | let 42 | typ = typeInst[1] 43 | return expandTypeInstImpl(typ) 44 | else: 45 | let 46 | impl = typeInst.getImpl 47 | if impl.kind == nnkTypeDef: 48 | let 49 | body = impl[2] 50 | if body.kind == nnkObjectTy: 51 | return node 52 | else: 53 | return expandTypeInstImpl(body) 54 | return typeInst 55 | elif node.kind == nnkInfix: 56 | case $node[0] 57 | of "*": 58 | return expandTypeInstImpl mulImpl(node[1], node[2]) 59 | of "/": 60 | return expandTypeInstImpl divImpl(node[1], node[2]) 61 | of "^": 62 | return expandTypeInstImpl powImpl(node[1], int node[2].intVal) 63 | else: 64 | error("internal error: expandTypeInstImpl. This should not be possible. Please submit an issue on GitHub so we can fix it.") 65 | 66 | result = node.copyNimNode 67 | for child in node.children: 68 | result.add expandTypeInstImpl(child) 69 | 70 | proc expandTypeInst(node: NimNode): NimNode = 71 | ## Expands a type node using ``getTypeInst``, until all generic parameters 72 | ## are resolved. 73 | result = expandTypeInstImpl node 74 | 75 | proc getTerms(dims: NimNode): seq[NimNode] = 76 | ## Retrieves all symbols inside a dimension or product dimension. 77 | result = newSeq[NimNode]() 78 | if dims.kind == nnkBracketExpr and dims[0].eqIdent("ProductDimension"): 79 | for dim in dims[1]: 80 | result.add ident($dim) 81 | elif dims.kind in {nnkSym, nnkIdent}: 82 | result.add ident($dims) 83 | else: 84 | error("internal error: getTerms. This should not be possible. Please submit an issue on GitHub so we can fix it.") 85 | result.sort(proc(x, y: NimNode): int = cmp($x, $y)) 86 | 87 | proc getNumDen*(dims: NimNode): tuple[num, den: seq[NimNode]] = 88 | ## Retrieves all symbols in the numerator of a dimension. 89 | let 90 | expanded = dims.expandTypeInst 91 | case expanded.kind 92 | of nnkBracketExpr: 93 | if expanded[0].eqIdent("QuotientDimension"): 94 | result.num = getTerms(expanded[1]) 95 | result.den = getTerms(expanded[2]) 96 | elif expanded[0].eqIdent("ProductDimension"): 97 | result.num = getTerms(expanded) 98 | result.den = newSeq[NimNode]() 99 | of nnkSym, nnkIdent: 100 | result.num = @[ident($expanded)] 101 | result.den = @[] 102 | else: 103 | error("internal error: getNumDen. This should not be possible. Please submit an issue on GitHub so we can fix it.") 104 | 105 | proc makeProductTerms(terms: seq[NimNode]): NimNode = 106 | ## Creates a single dimension node, or a product node from a set 107 | ## of terms to be multiplied. 108 | if terms.len == 0: 109 | result = ident"DimensionLess" 110 | elif terms.len == 1: 111 | result = terms[0] 112 | else: 113 | result = newTree(nnkBracketExpr, 114 | bindsym"ProductDimension", 115 | newTree(nnkPar).add terms 116 | ) 117 | 118 | proc divide(oldNum, oldDen: seq[NimNode]): tuple[ 119 | num, den: seq[NimNode]] = 120 | ## Performs the division of a numerator and denominator sequence. 121 | var 122 | totalNum = oldNum 123 | totalDen = oldDen 124 | totalNum.sort(proc(x, y: NimNode): int = cmp($x, $y)) 125 | totalDen.sort(proc(x, y: NimNode): int = cmp($x, $y)) 126 | result.num = newSeq[NimNode]() 127 | result.den = newSeq[NimNode]() 128 | for num in totalNum: 129 | if num.eqIdent("DimensionLess"): 130 | continue 131 | block divide: 132 | var 133 | idx = 0 134 | for den in totalDen: 135 | if num == den: 136 | totalDen.del idx 137 | break divide 138 | inc idx 139 | result.num.add num 140 | for den in totalDen: 141 | if den.eqIdent("DimensionLess"): 142 | continue 143 | result.den.add den 144 | 145 | proc makeQuotientTerms(num, den: seq[NimNode]): NimNode = 146 | ## Creates a single dimension node, or a quotient node from a set of terms 147 | ## to be divided. 148 | let 149 | (num, den) = divide(num, den) 150 | if den.len == 0: 151 | result = makeProductTerms(num) 152 | else: 153 | result = newTree(nnkBracketExpr, 154 | ident"QuotientDimension", 155 | makeProductTerms(num), 156 | makeProductTerms(den)) 157 | 158 | proc mulImpl(x, y: NimNode): NimNode = 159 | var 160 | (xNum, xDen) = getNumDen(x) 161 | (yNum, yDen) = getNumDen(y) 162 | totalNum = xNum 163 | totalDen = xDen 164 | totalNum.add yNum 165 | totalDen.add yDen 166 | totalNum.sort(proc(x, y: NimNode): int = cmp($x, $y)) 167 | totalDen.sort(proc(x, y: NimNode): int = cmp($x, $y)) 168 | result = makeQuotientTerms(totalNum, totalDen) 169 | 170 | macro `*`*(x: SomeDimension, y: distinct SomeDimension): untyped = 171 | ## The ``ProductDimension`` between two dimensions. 172 | result = mulImpl(x, y) 173 | 174 | proc divImpl(x, y: NimNode): NimNode = 175 | var 176 | (xNum, xDen) = getNumDen(x) 177 | (yNum, yDen) = getNumDen(y) 178 | totalNum = xNum 179 | totalDen = xDen 180 | totalNum.add yDen 181 | totalDen.add yNum 182 | totalNum.sort(proc(x, y: NimNode): int = cmp($x, $y)) 183 | totalDen.sort(proc(x, y: NimNode): int = cmp($x, $y)) 184 | result = makeQuotientTerms(totalNum, totalDen) 185 | 186 | macro `/`*(x: SomeDimension, y: distinct SomeDimension): untyped = 187 | ## The ``QuotientDimension`` between two dimensions. 188 | result = divImpl(x, y) 189 | 190 | proc powImpl(x: NimNode; y: int): NimNode = 191 | var 192 | (xNum, xDen) = getNumDen(x) 193 | totalNum = xNum.cycle(y) 194 | totalDen = xDen.cycle(y) 195 | result = makeQuotientTerms(totalNum, totalDen) 196 | 197 | macro `^`*(x: SomeDimension, y: static[int]): untyped = 198 | ## The ``ProductDimension`` resulting from taking the ``yth`` power of a 199 | ## dimension. 200 | result = powImpl(x, y) 201 | 202 | macro stringify*(dim: SomeDimension): string = 203 | ## Stringifies any dimension type. 204 | let 205 | (num, den) = dim.getNumDen 206 | 207 | var 208 | numerator = initTable[string, int]() 209 | denominator = initTable[string, int]() 210 | 211 | for elem in num: 212 | numerator.mgetOrPut($elem, 0) += 1 213 | for elem in den: 214 | denominator.mgetOrPut($elem, 0) += 1 215 | 216 | if numerator.len == 0 and denominator.len == 0: 217 | result = newStrLitNode("1 ") 218 | elif numerator.len == 0: 219 | result = newStrLitNode("1 / ") 220 | for key in denominator.keys: 221 | var 222 | id = ident key 223 | times = denominator[key] 224 | toAppend = quote do: 225 | $`id` & " " 226 | if denominator[key] > 1: 227 | toAppend = quote do: 228 | $`id` & "^" & $`times` & " " 229 | result = newTree(nnkCall, 230 | bindsym"&", 231 | result.copyNimTree, 232 | toAppend 233 | ) 234 | elif denominator.len == 0: 235 | result = newStrLitNode("") 236 | for key in numerator.keys: 237 | var 238 | id = ident key 239 | times = numerator[key] 240 | toAppend = quote do: 241 | $`id` & " " 242 | if numerator[key] > 1: 243 | toAppend = quote do: 244 | $`id` & "^" & $`times` & " " 245 | result = newTree(nnkCall, 246 | bindsym"&", 247 | result.copyNimTree, 248 | toAppend 249 | ) 250 | else: 251 | result = newStrLitNode("") 252 | for key in numerator.keys: 253 | var 254 | id = ident key 255 | times = numerator[key] 256 | toAppend = quote do: 257 | $`id` & " " 258 | if numerator[key] > 1: 259 | toAppend = quote do: 260 | $`id` & "^" & $`times` & " " 261 | result = newTree(nnkCall, 262 | bindsym"&", 263 | result.copyNimTree, 264 | toAppend 265 | ) 266 | result = newTree(nnkCall, 267 | bindsym"&", 268 | result.copyNimTree, 269 | newStrLitNode("/ ") 270 | ) 271 | for key in denominator.keys: 272 | var 273 | id = ident key 274 | times = denominator[key] 275 | toAppend = quote do: 276 | $`id` & " " 277 | if denominator[key] > 1: 278 | toAppend = quote do: 279 | $`id` & "^" & $`times` & " " 280 | result = newTree(nnkCall, 281 | bindsym"&", 282 | result.copyNimTree, 283 | toAppend 284 | ) 285 | result = quote do: 286 | `result`[0 ..< `result`.len - 1] 287 | 288 | macro `$`*(dim: SomeDimension): string = 289 | ## Stringifies a dimension type. 290 | result = quote do: 291 | stringify(`dim`) 292 | -------------------------------------------------------------------------------- /src/metric/natural.nim: -------------------------------------------------------------------------------- 1 | # 2 | # metric - bringing the SI to Nim. 3 | # 4 | # (c) Copyright 2018 Michael Jendrusch. All rights reserved. 5 | # This library is licensed under the MIT license. 6 | # For more information see LICENSE. 7 | 8 | ## This module provides Planck units, i.e. ``h = c = G = kB = ke = 1``. 9 | 10 | import dimension, unit, stddim, siunits 11 | import constants 12 | 13 | const 14 | planckLength* = 1.616e-35 * meter 15 | planckArea* = planckLength ^ 2 16 | planckVolume* = planckLength ^ 3 17 | planckMass* = 2.176e-8 * kilogram 18 | planckTime* = 5.3912e-44 * second 19 | planckTemperature* = 1.417e32 * kelvin 20 | planckCharge* = 1.876e-18 * coulomb 21 | planckCurrent* = planckCharge / planckTime 22 | -------------------------------------------------------------------------------- /src/metric/siunits.nim: -------------------------------------------------------------------------------- 1 | # 2 | # metric - bringing the SI to Nim. 3 | # 4 | # (c) Copyright 2018 Michael Jendrusch. All rights reserved. 5 | # This library is licensed under the MIT license. 6 | # For more information see LICENSE. 7 | 8 | import dimension, unit, stddim 9 | 10 | const 11 | meter* = Metric[Length, float](val: 1.0) 12 | second* = Metric[Time, float](val: 1.0) 13 | kilogram* = Metric[Mass, float](val: 1.0) 14 | mole* = Metric[Amount, float](val: 1.0) 15 | kelvin* = Metric[Temperature, float](val: 1.0) 16 | ampere* = Metric[Current, float](val: 1.0) 17 | candela* = Metric[Intensity, float](val: 1.0) 18 | one* = Metric[Dimensionless, float](val: 1.0) 19 | 20 | # derived units: 21 | hertz* = one / second ## \ 22 | ## An SI-compatible unit of frequency. 23 | newton* = kilogram * meter / second ^ 2 ## \ 24 | ## An SI-compatible unit of force. 25 | pascal* = newton / meter ^ 2 ## \ 26 | ## An SI-compatible unit of pressure. 27 | joule* = newton * meter ## \ 28 | ## An SI-compatible unit of energy. 29 | watt* = joule / second ## \ 30 | ## An SI-compatible unit of power. 31 | coulomb* = ampere * second ## \ 32 | ## An SI-compatible unit of charge. 33 | volt* = watt / ampere ## \ 34 | ## An SI-compatible unit of electric potential difference. 35 | farad* = coulomb / volt ## \ 36 | ## An SI-compatible unit of capacitance. 37 | ohm* = volt / ampere ## \ 38 | ## An SI-compatible unit of resistance. 39 | siemens* = one / ohm ## \ 40 | ## An SI-compatible unit of conductance. 41 | weber* = volt * second ## \ 42 | ## An SI-compatible unit of magnetic flux. 43 | tesla* = weber / meter ^ 2 ## \ 44 | ## An SI-compatible unit of magnetic flux density. 45 | henry* = weber / ampere ## \ 46 | ## An SI-compatible unit of inductance. 47 | lux* = candela / meter ^ 2 ## \ 48 | ## An SI-compatible unit of illuminance. 49 | becquerel* = one / second ^ 2 ## \ 50 | ## An SI-compatible unit of radioactivity. 51 | gray* = meter ^ 2 / second ^ 2 ## \ 52 | ## An SI-compatible unit of absorbed radiation dose. 53 | sievert* = meter ^ 2 / second ^ 2 ## \ 54 | ## An SI-compatible unit of equivalent radiation dose. 55 | katal* = mole / second ## \ 56 | ## An SI-compatible unit of enzyme activity. 57 | 58 | template deci*(x: auto): auto = 1e-1 * x 59 | template centi*(x: auto): auto = 1e-2 * x 60 | template milli*(x: auto): auto = 1e-3 * x 61 | template micro*(x: auto): auto = 1e-6 * x 62 | template nano*(x: auto): auto = 1e-9 * x 63 | template pico*(x: auto): auto = 1e-12 * x 64 | template femto*(x: auto): auto = 1e-15 * x 65 | template atto*(x: auto): auto = 1e-18 * x 66 | template zepto*(x: auto): auto = 1e-21 * x 67 | template yocto*(x: auto): auto = 1e-24 * x 68 | template deca*(x: auto): auto = 1e1 * x 69 | template hecto*(x: auto): auto = 1e2 * x 70 | template kilo*(x: auto): auto = 1e3 * x 71 | template mega*(x: auto): auto = 1e6 * x 72 | template giga*(x: auto): auto = 1e9 * x 73 | template tera*(x: auto): auto = 1e12 * x 74 | template peta*(x: auto): auto = 1e15 * x 75 | template exa*(x: auto): auto = 1e18 * x 76 | template zeta*(x: auto): auto = 1e21 * x 77 | template yotta*(x: auto): auto = 1e24 * x 78 | 79 | template withUnits*(body: untyped): untyped {. dirty .} = 80 | ## Allows to use unit constants in a scoped manner. 81 | block: 82 | const 83 | # common shorthands: 84 | # length: 85 | m {. used .} = meter 86 | mm {. used .} = milli m 87 | cm {. used .} = centi m 88 | dm {. used .} = deci m 89 | km {. used .} = kilo m 90 | 91 | inch {. used .} = 2.54 * cm 92 | foot {. used .} = 12.0 * inch 93 | yard {. used .} = 3.0 * foot 94 | mile {. used .} = 1760.0 * yard 95 | 96 | # area 97 | a {. used .} = 100.0 * m ^ 2 98 | ha {. used .} = hecto a 99 | 100 | # volume 101 | L {. used .} = dm ^ 3 102 | mL {. used .} = milli L 103 | µL {. used .} = micro L 104 | nL {. used .} = nano L 105 | pL {. used .} = pico L 106 | fL {. used .} = femto L 107 | 108 | # time: 109 | s {. used .} = second 110 | min {. used .} = 60.0 * second 111 | hour {. used .} = 60.0 * min 112 | day {. used .} = 24.0 * hour 113 | week {. used .} = 7.0 * day 114 | 115 | # mass: 116 | g {. used .} = 1e-3 * kilogram 117 | kg {. used .} = kilo g 118 | t {. used .} = mega g 119 | mg {. used .} = milli g 120 | µg {. used .} = micro g 121 | ng {. used .} = nano g 122 | pg {. used .} = pico g 123 | fg {. used .} = femto g 124 | 125 | # amount: 126 | mol {. used .} = mole 127 | mmol {. used .} = milli mol 128 | µmol {. used .} = micro mol 129 | nmol {. used .} = nano mol 130 | pmol {. used .} = pico mol 131 | fmol {. used .} = femto mol 132 | 133 | # concentration: 134 | M {. used .} = mol / L 135 | mMolar {. used .} = milli M 136 | µMolar {. used .} = micro M 137 | nMolar {. used .} = nano M 138 | pMolar {. used .} = pico M 139 | fMolar {. used .} = femto M 140 | 141 | # temperature: 142 | K {. used .} = kelvin 143 | 144 | # current: 145 | A {. used .} = ampere 146 | kA {. used .} = kilo A 147 | mA {. used .} = milli A 148 | 149 | # intensity: 150 | cd {. used .} = candela 151 | 152 | # derived shorthands: 153 | Pa {. used .} = pascal 154 | J {. used .} = joule 155 | W {. used .} = watt 156 | C {. used .} = coulomb 157 | V {. used .} = volt 158 | F {. used .} = farad 159 | S {. used .} = siemens 160 | Wb {. used .} = weber 161 | T {. used .} = tesla 162 | H {. used .} = henry 163 | lx {. used .} = lux 164 | Bq {. used .} = becquerel 165 | Gy {. used .} = gray 166 | Sv {. used .} = sievert 167 | kat {. used .} = katal 168 | N {. used .} = newton 169 | body 170 | -------------------------------------------------------------------------------- /src/metric/stddim.nim: -------------------------------------------------------------------------------- 1 | import dimension 2 | 3 | type 4 | Length* = object of BaseDimension 5 | Time* = object of BaseDimension 6 | Mass* = object of BaseDimension 7 | Amount* = object of BaseDimension 8 | Temperature* = object of BaseDimension 9 | Current* = object of BaseDimension 10 | Intensity* = object of BaseDimension 11 | Area* = Length ^ 2 12 | Volume* = Length ^ 3 13 | Velocity* = Length / Time 14 | Acceleration* = Velocity / Time 15 | Momentum* = Mass * Velocity 16 | Force* = Mass * Acceleration 17 | Energy* = Force * Length 18 | Action* = Energy * Time 19 | Frequency* = Dimensionless / Time 20 | Pressure* = Force / Area 21 | Power* = Energy / Time 22 | Charge* = Current * Time 23 | Voltage* = Energy / Current 24 | Capacitance* = Charge / Voltage 25 | Resistance* = Voltage / Current 26 | Conductance* = Dimensionless / Voltage 27 | MagneticFlux* = Voltage * Time 28 | MagneticFluxDensity* = MagneticFlux / Area 29 | Inductance* = MagneticFlux / Current 30 | Illuminance* = Intensity / Area 31 | Radioactivity* = Dimensionless / Time ^ 2 32 | RadiationDose* = Energy / Mass 33 | RadiationEffectiveDose* = Energy / Mass 34 | CatalyticActivity* = Amount / Time 35 | 36 | template `$`*(x: typedesc[Length]): string = "m" 37 | template `$`*(x: typedesc[Time]): string = "s" 38 | template `$`*(x: typedesc[Mass]): string = "kg" 39 | template `$`*(x: typedesc[Amount]): string = "mol" 40 | template `$`*(x: typedesc[Temperature]): string = "K" 41 | template `$`*(x: typedesc[Current]): string = "A" 42 | template `$`*(x: typedesc[Intensity]): string = "cd" 43 | template `$`*(x: typedesc[Dimensionless]): string = "1" 44 | -------------------------------------------------------------------------------- /src/metric/unit.nim: -------------------------------------------------------------------------------- 1 | # 2 | # metric - bringing the SI to Nim. 3 | # 4 | # (c) Copyright 2018 Michael Jendrusch. All rights reserved. 5 | # This library is licensed under the MIT license. 6 | # For more information see LICENSE. 7 | 8 | import math, strformat 9 | import dimension 10 | 11 | type 12 | Metric*[D, T] = object 13 | ## A value endowed with dimension. 14 | val*: T 15 | Unit*[D] = Metric[D, float] 16 | 17 | proc `+`*[U, X](x, y: Metric[U, X]): auto = 18 | ## Sum of two dimensionful quantities. 19 | let 20 | res = x.val + y.val 21 | Metric[U, X](val: res) 22 | 23 | proc `-`*[U, X](x, y: Metric[U, X]): auto = 24 | ## Difference of two dimensionful quantities. 25 | let 26 | res = x.val - y.val 27 | Metric[U, X](val: res) 28 | 29 | proc `*`*[U, X](x: Metric[U, X]; y: X): auto = 30 | ## Product of a number and a dimensionful quantity. 31 | let 32 | res = x.val * y 33 | Metric[U, type res](val: res) 34 | 35 | proc `*`*[U, X](x: X; y: Metric[U, X]): auto = 36 | ## Product of a number and a dimensionful quantity. 37 | let 38 | res = x * y.val 39 | Metric[U, type res](val: res) 40 | 41 | proc `*`*[U, V, X, Y](x: Metric[U, X]; y: Metric[V, Y]): auto = 42 | ## Product of two dimensionful quantities. 43 | let 44 | res = x.val * y.val 45 | when U * V is Dimensionless: 46 | result = res 47 | else: 48 | Metric[U * V, type res](val: res) 49 | 50 | proc `/`*[U, X](x: Metric[U, X]; y: X): auto = 51 | ## Quotient of a number and a dimensionful quantity. 52 | let 53 | res = x.val / y 54 | Metric[U, type res](val: res) 55 | 56 | proc `/`*[U, X](x: X; y: Metric[U, X]): auto = 57 | ## Quotient of a number and a dimensionful quantity. 58 | let 59 | res = x / y.val 60 | Metric[U, type res](val: res) 61 | 62 | proc `/`*[U, V, X, Y](x: Metric[U, X]; y: Metric[V, Y]): auto = 63 | ## Quotient of two dimensionful quantities. 64 | let 65 | res = x.val / y.val 66 | when U / V is Dimensionless: 67 | result = res 68 | else: 69 | Metric[U / V, type res](val: res) 70 | 71 | proc `^`*[U, X](x: Metric[U, X]; y: static[Natural]): auto = 72 | ## Natural power of a dimensionful quantity. 73 | let 74 | res = pow(x.val, y) 75 | Metric[U ^ y, type res](val: res) 76 | 77 | {. experimental .} 78 | 79 | template setAs*[U, X](x: var Metric[U, X]; units: Metric[U, X]; y: X) = 80 | ## Sets ``x`` to ``y`` in units of ``units``. 81 | x.val = units.val * y 82 | 83 | template `.=`*[U, X](x: var Metric[U, X]; units: Metric[U, X]; y: X) = 84 | ## Sets ``x`` to ``y`` in units of ``units``. 85 | x.val = units.val * y 86 | 87 | template `as`*[U, X](x: Metric[U, X]; units: Metric[U, X]): X = 88 | ## Retrieves the value of ``x`` in units of ``units`` 89 | x.val / units.val 90 | 91 | template `.`*[U, X](x: Metric[U, X]; units: Metric[U, X]): X = 92 | ## Retrieves the value of ``x`` in units of ``units`` 93 | x.val / units.val 94 | 95 | proc `$`*[U, X](x: Metric[U, X]): string = fmt"{x.val} [{U}]" 96 | proc toStringWithUnit*[U, X](x: Metric[U, X]; unit: Metric[U, X]; 97 | expression: string): string = 98 | ## Stringifies a dimensionful value, with a given unit and unit-expression. 99 | let 100 | unitVal = x.unit 101 | fmt"{unitVal} [{expression}]" 102 | -------------------------------------------------------------------------------- /tests/tall.nim: -------------------------------------------------------------------------------- 1 | # 2 | # metric - bringing the SI to Nim. 3 | # 4 | # (c) Copyright 2018 Michael Jendrusch. All rights reserved. 5 | # This library is licensed under the MIT license. 6 | # For more information see LICENSE. 7 | 8 | import metric 9 | import unittest, strformat 10 | 11 | suite "metric tests": 12 | test "unit consistency": 13 | var 14 | length = 10.0 * meter 15 | area = length ^ 2 16 | area2 = 10.0 * meter * meter 17 | time = second 18 | otherTime = Unit[Time](val: 10.0) 19 | mass = kilogram 20 | combined = length * time ^ 2 / mass 21 | dimless = length / length 22 | force {. used .}: Unit[Force] = kilogram * meter / second ^ 2 23 | check dimless is float 24 | check compiles(otherTime + time) 25 | check compiles(area + area2) 26 | check (not(compiles(area + length))) 27 | check compiles(area + combined * kilogram / second ^ 2 * meter * 3.14) 28 | 29 | test "unit interconversion": 30 | const 31 | milliMeterSquared = (milli meter) ^ 2 32 | hektar = 10_000.0 * meter ^ 2 33 | var 34 | area = meter ^ 2 35 | check area.hektar == 0.0001 36 | area.hektar = 10 37 | check area.milliMeterSquared == 1e11 38 | check (not(compiles(area.meter))) 39 | 40 | test "unit macro": 41 | withUnits: 42 | var 43 | tenMilliMeters = 10.0 * mm 44 | oneCentiMeter = 1.0 * cm 45 | check tenMilliMeters == oneCentiMeter 46 | check tenMilliMeters.cm == 1.0 47 | 48 | test "pretty printing": 49 | check $Force == "kg m / s^2" 50 | check $(meter * meter / second^2 * ampere) == "1.0 [m^2 A / s^2]" 51 | check toStringWithUnit(10.0 * meter / second, 52 | milli(meter) / second, 53 | "mm / s") == "10000.0 [mm / s]" 54 | check fmt"{10.0 * meter as milli meter} [mm]" == "10000.0 [mm]" 55 | --------------------------------------------------------------------------------