{"id":26600803,"url":"https://github.com/scinim/unchained","last_synced_at":"2025-04-09T16:24:00.538Z","repository":{"id":44550965,"uuid":"338718871","full_name":"SciNim/Unchained","owner":"SciNim","description":"A fully type safe, compile time only units library.","archived":false,"fork":false,"pushed_at":"2024-07-25T10:18:31.000Z","size":477,"stargazers_count":112,"open_issues_count":3,"forks_count":0,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-23T18:47:47.217Z","etag":null,"topics":["compile-time","hacktoberfest","meta-programming","nim","nim-lang","type-safety","units"],"latest_commit_sha":null,"homepage":"https://scinim.github.io/Unchained","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SciNim.png","metadata":{"files":{"readme":"README.org","changelog":"changelog.org","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-14T03:09:35.000Z","updated_at":"2025-02-22T15:54:37.000Z","dependencies_parsed_at":"2023-01-29T23:00:34.550Z","dependency_job_id":"e7d428a5-882e-4de8-b6d7-1d4a0fb78805","html_url":"https://github.com/SciNim/Unchained","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SciNim%2FUnchained","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SciNim%2FUnchained/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SciNim%2FUnchained/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SciNim%2FUnchained/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SciNim","download_url":"https://codeload.github.com/SciNim/Unchained/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248066006,"owners_count":21042015,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compile-time","hacktoberfest","meta-programming","nim","nim-lang","type-safety","units"],"created_at":"2025-03-23T18:35:29.007Z","updated_at":"2025-04-09T16:24:00.522Z","avatar_url":"https://github.com/SciNim.png","language":"Nim","readme":"* Unchained - Compile time only units checking\n[[https://github.com/SciNim/unchained/workflows/unchained%20CI/badge.svg]]\n\n=Unchained= is a fully type safe, compile time only units\nlibrary. There is *absolutely no* performance loss over pure =float=\nbased code (aside from insertion of possible conversion factors, but\nthose would have to be written by hand otherwise of course).\n\nIt supports:\n- all base SI units and (most) compound SI units\n- units as short and long name:\n  #+begin_src nim\nimport unchained\nlet x = 10.m\nlet y = 10.Meter\ndoAssert x == y\n  #+end_src\n- some imperial units\n- all SI prefixes\n  #+begin_src nim\nimport unchained\nlet x = 10.Mm # mega meter\nlet y = 5.ng # nano gram\nlet z = 10.aT # atto tesla \n  #+end_src\n- arbitrary math with units composing to new units, e.g. (which do not have\n  to be defined previously!),\n  #+begin_src nim\nimport unchained\nlet x = 10.m * 10.m * 10.m * 10.m * 10.m\ndoAssert typeof(x) is Meter⁵\n  #+end_src\n  without having to predefine a =Meter⁵= type\n- automatic conversion between SI prefixes if a mix is used\n  #+begin_src nim\nimport unchained\nlet x = 5.kg + 5.lbs\ndoAssert typeof(x) is kg\ndoAssert x == 7.26796.kg\n  #+end_src\n- manual conversion of units to compatible other units via ~to~\n  (e.g. \n  #+begin_src nim\nimport unchained\nlet x = 5.m•s⁻¹\ndefUnit(km•h⁻¹) # needs to be defined to be able to convert to\n                # `to` could be a macro that defines it for us \ndoAssert x.to(km•h⁻¹) == 18.km•h⁻¹\n# the `toDef` macro can be used to both define and convert a unit,\n# but under certain use cases it can break (see its documentation)\n  #+end_src\n- comparisons between units compare real value taking into account SI\n  prefixes and even different units of the same quantity:  \n#+begin_src nim\nimport unchained\nlet x = 10.Mm # mega meter\ndoAssert x == 10_000_000.m\nlet y = 5.ng # nano gram\ndoAssert y == 5e-9.g\nlet z = 10.aT # atto tesla\ndoAssert z == 10e-18.T\n# and even different units of same quantity\nlet a = 5000.inch•s⁻¹\nlet b = a.toDef(km•h⁻¹) # defines the unit and convers `a` to it\ndoAssert b == 457.2.km•h⁻¹\ndoAssert typeof(a) is inch•s⁻¹ # SI units have higher precedence than non SI\ndoAssert typeof(b) is km•h⁻¹\ndoAssert a == b # comparison is true, as the effective value is the same!\n#+end_src\n  Note: comparison between units is performed using an ~almostEqual~\n  implementation. By default it uses ~ε = 1e-8~. The power can be\n  changed at CT by using the ~-d:UnitCompareEpsilon=\u003cinteger\u003e~ where\n  the given integer is the negative power used.\n- all quantities (e.g. ~Length~, ~Mass~, ...) defined as a ~concept~\n  to allow matching different units of same quantity in function\n  argument\n  #+begin_src nim\nimport unchained\nproc force[M: Mass, A: Acceleration](m: M, a: A): Force = m * a\nlet m = 80.kg\nlet g = 9.81.m•s⁻²\nlet f = force(m, g)\ndoAssert typeof(f) is Newton\ndoAssert f == 784.8.N\n  #+end_src\n- define your own custom unit systems, see [[examples/custom_unit_system.nim]]  \n- ...\n\nA longer snippet showing different features below. See also\n[[examples/bethe_bloch.nim]] for a more complicated use case.\n#+begin_src nim\nimport unchained\nblock:\n  # defining simple units\n  let mass = 5.kg\n  let a = 9.81.m•s⁻²\nblock:\n  # addition and subtraction of same units\n  let a = 5.kg\n  let b = 10.kg\n  doAssert typeof(a + b) is KiloGram\n  doAssert a + b == 15.kg\n  doAssert typeof(a - b) is KiloGram\n  doAssert a - b == -5.kg\nblock:\n  # addition and subtraction of units of the same ``quantity`` but different scale\n  let a = 5.kg\n  let b = 500.g\n  doAssert typeof(a + b) is KiloGram\n  doAssert a + b == 5.5.kg\n  # if units do not match, the SI unit is used!\nblock:\n  # product of prefixed SI unit keeps same prefix unless multiple units of same quantity involved\n  let a = 1.m•s⁻²\n  let b = 500.g\n  doAssert typeof(a * b) is Gram•Meter•Second⁻²\n  doAssert typeof((a * b).to(MilliNewton)) is MilliNewton\n  doAssert a * b == 500.g•m•s⁻²\nblock:\n  let mass = 5.kg\n  let a = 9.81.m•s⁻²\n  # unit multiplication has to be commutative\n  let F: Newton = mass * a\n  let F2: Newton = a * mass\n  # unit division works as expected\n  doAssert typeof(F / mass) is N•kg⁻¹\n  doAssert typeof((F / mass).to(Meter•Second⁻²)) is Meter•Second⁻²\n  doAssert F / mass == a\nblock:\n  # automatic deduction of compound units for simple cases\n  let force = 1.kg * 1.m * 1.s⁻²\n  echo force # 1 Newton\n  doAssert typeof(force) is Newton\nblock:\n  # conversion between units of the same quantity\n  let f = 10.N\n  doAssert typeof(f.to(kN)) is KiloNewton\n  doAssert f.to(kN) == 0.01.kN\nblock:\n  # pre-defined physical constants\n  let E_e⁻_rest: Joule = m_e * c*c # math operations `*cannot*` use superscripts!\n  # m_e = electron mass in kg\n  # c = speed of light in vacuum in m/s\nfrom std/math import sin  \nblock:\n  # automatic CT error if argument of e.g. sin, ln are not unit less\n  let x = 5.kg\n  let y = 10.kg\n  discard sin(x / y) ## compiles gives correct result (~0.48)\n  let x2 = 10.m\n  # sin(x2 / y) ## errors at CT due to non unit less argument\nblock:\n  # imperial units\n  let mass = 100.lbs\n  let distance = 100.inch\nblock:\n  # mixing of non SI and SI units (via conversion to SI units)\n  let m1 = 100.lbs\n  let m2 = 10.kg\n  doAssert typeof(m1 + m2) is KiloGram\n  doAssert m1 + m2 == 55.359237.KiloGram\nblock:\n  # natural unit conversions\n  let speed = (0.1 * c).toNaturalUnit() # fraction of c, defined in `constants`\n  let m_e = 9.1093837015e-31.kg.toNaturalUnit()\n  # math between natural units remains natural\n  let p = speed * m_e # result will be in `eV`\n  doAssert p.to(keV) == 51.099874.keV\n\n## If there is demand the following kind of syntax may be implemented in the future\nwhen false:\n  # units using english language (using accented quotes)\n  let a = 10.`meter per second squared`\n  let b = 5.`kilogram meter per second squared`\n  check typeof(a) is Meter•Second⁻²\n  check typeof(b) is Newton\n  check a == 10.m•s⁻²\n  check b == 5.N\n#+end_src\n\nThings to note:\n- real units use capital letters and are verbose\n- shorthands defined for all typical units using their common\n  abbreviation (upper or lower case depending on the unit, e.g. ~s~ (second)\n  and ~N~ (Newton)\n- conversion of numbers to units done using `.` call and using\n  shorthand names  \n- `•` symbol is product of units to allow unambiguous parsing of units\n  -\u003e specific unicode symbol may become user customizable in the future\n- no division of units, but negative exponents\n- exponents are in superscript\n- usage of `•` and superscript is to circumvent Nim's identifier\n  rules!\n- SI units are the base. If ambiguous operation that can be solved by\n  unit conversion, SI units are used (in the default SI unit system\n  predefined when simply importing ~unchained~)\n- math operations *cannot* use superscripts!\n- some physical constants are defined, more likely in the future \n- conversion from prefixed SI unit to non prefixed SI unit *only*\n  happens if multiple prefixed units of same quantity involved\n- =UnitLess= is a =distinct float= unit that has a converter to\n  =float= (such that =UnitLess= magically works with math functions\n  expecting floats).\n\n** Why \"Unchained\"?\nUn = Unit\nChain = [[https://en.wikipedia.org/wiki/Chain_(unit)][A unit]]\n\nYou shall be unchained from the shackles of dealing with painful\nerrors due to unit mismatches by using this lib! Tada!\n\n*Hint*: The unit =Chain= does not exist in this library...\n\n** Units and ~cligen~\n\n~cligen~ is arguably the most powerful and at the same time convenient\nto use command line argument parser in Nim land (and likely across\nlanguages...; plus a lot of other things!).\n\nFor that reason it is a common desire to combine ~Unchained~ units as\nan command line argument to a program that uses ~cligen~ to parse the\narguments. Thanks to ~cligen's~ extensive options to expand its\nfeatures, we now provide a simple submodule you can import in order to\nsupport ~Unchained~ units in your program. Here's a short example\nuseful for the runners among you, a simple script to convert a given\nspeed (in mph, km/h or m/s) to a time per minute / per mile / 5K / 10K\n/ ... distance or vice versa:\n#+begin_src nim :tangle examples/speed_tool.nim\nimport unchained, math, strutils\ndefUnit(mi•h⁻¹)\ndefUnit(km•h⁻¹)\ndefUnit(m•s⁻¹)\nproc timeStr[T: Time](t: T): string =\n  let (h, mr) = splitDecimal(t.to(Hour).float)\n  let (m, s)  = splitDecimal(mr.Hour.to(Minute).float)\n  result =\n    align(pretty(h.Hour, 0, true, ffDecimal), 6, ' ') \u0026\n    \" \" \u0026 align(pretty(m.Minute, 0, true, ffDecimal), 8, ' ') \u0026\n    \" \" \u0026 align(pretty(s.Minute.to(Second), 0, true, ffDecimal), 6, ' ')\ntemplate print(d, x) = echo \"$#: $#\" % [alignLeft(d, 9), align(x, 10)]\nproc echoTimes[V: Velocity](v: V) =\n  print(\"1K\",       timeStr 1.0 / (v / 1.km))\n  print(\"1 mile\",   timeStr 1.0 / (v / 1.Mile))\n  print(\"5K\",       timeStr 1.0 / (v / 5.km))\n  print(\"10K\",      timeStr 1.0 / (v / 10.km))\n  print(\"Half\",     timeStr 1.0 / (v / (42.195.km / 2.0)))\n  print(\"Marathon\", timeStr 1.0 / (v / 42.195.km))\n  print(\"50K\",      timeStr 1.0 / (v / 50.km))\n  print(\"100K\",     timeStr 1.0 / (v / 100.km))   # maybe a bit aspirational at the same pace, huh?\n  print(\"100 mile\", timeStr 1.0 / (v / 100.Mile)) # let's hope it's not Leadville\nproc mph(v: mi•h⁻¹) = echoTimes(v)\nproc kmh(v: km•h⁻¹) = echoTimes(v)\nproc mps(v:  m•s⁻¹) = echoTimes(v)\nproc speed(d: km, hour = 0.0.h, min = 0.0.min, sec = 0.0.s) =\n  let t = hour + min + sec\n  print(\"km/h\", pretty((d / t).to(km•h⁻¹), 2, true))\n  print(\"mph\",  pretty((d / t).to(mi•h⁻¹), 2, true))\n  print(\"m/s\",  pretty((d / t).to( m•s⁻¹), 2, true))\nwhen isMainModule:\n  import unchained / cligenParseUnits # just import this and then you can use `unchained` units as parameters!\n  import cligen\n  dispatchMulti([mph], [kmh], [mps], [speed])\n#+end_src\n\n#+begin_src sh :results drawer\nnim c examples/speed_tool\nexamples/speed_tool mph -v 7.0 # without unit, assumed is m•h⁻¹\necho \"----------------------------------------\"\nexamples/speed_tool kmh -v 12.5.km•h⁻¹ # with explicit unit\necho \"----------------------------------------\"\nexamples/speed_tool speed -d 11.24.km --min 58 --sec 4\n#+end_src\n\n#+RESULTS:\n:results:\n1K       :  0 h  5 min 20 s\n1 mile   :  0 h  8 min 34 s\n5K       :  0 h 26 min 38 s\n10K      :  0 h 53 min 16 s\nHalf     :  1 h 52 min 22 s\nMarathon :  3 h 44 min 44 s\n50K      :  4 h 26 min 18 s\n100K     :  8 h 52 min 36 s\n100 mile : 14 h 17 min  9 s\n----------------------------------------\n1K       :  0 h  4 min 48 s\n1 mile   :  0 h  7 min 43 s\n5K       :  0 h 24 min  0 s\n10K      :  0 h 48 min  0 s\nHalf     :  1 h 41 min 16 s\nMarathon :  3 h 22 min 32 s\n50K      :  4 h  0 min  0 s\n100K     :  8 h  0 min  0 s\n100 mile : 12 h 52 min 29 s\n----------------------------------------\nkm/h     : 12 km•h⁻¹\nmph      : 7.2 mi•h⁻¹\nm/s      : 3.2 m•s⁻¹\n:end:\n\nwhich outputs:\n#+begin_src sh\n1K       :  0 h  5 min 20 s\n1 mile   :  0 h  8 min 34 s\n5K       :  0 h 26 min 38 s\n10K      :  0 h 53 min 16 s\nHalf     :  1 h 52 min 22 s\nMarathon :  3 h 44 min 44 s\n50K      :  4 h 26 min 18 s\n100K     :  8 h 52 min 36 s\n100 mile : 14 h 17 min  9 s\n----------------------------------------\n1K       :  0 h  4 min 48 s\n1 mile   :  0 h  7 min 43 s\n5K       :  0 h 24 min  0 s\n10K      :  0 h 48 min  0 s\nHalf     :  1 h 41 min 16 s\nMarathon :  3 h 22 min 32 s\n50K      :  4 h  0 min  0 s\n100K     :  8 h  0 min  0 s\n100 mile : 12 h 52 min 29 s\n----------------------------------------\nkm/h     : 12 km•h⁻¹\nmph      : 7.2 mi•h⁻¹\nm/s      : 3.2 m•s⁻¹\n#+end_src\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscinim%2Funchained","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscinim%2Funchained","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscinim%2Funchained/lists"}