A units package that uses a very similar mechanism for unit tracking as DynamicQuantities.jl, but has an implementation that is more similar to Unitful.jl. The main difference comes from design decisions centered around flexibility and ambiguity avoidance, which are issues that sometimes come up when I build industrial solutions with existing units packages.
The main design choice that enables flexibility comes from its implementation which is similar to DynamicQuantities.jl, where the fundamental building block is a Dimension
object that tracks the (SI) dimensions of the unit. In this package, one level above a Dimension
is an AffineUnit
which adds a scale and an offset and can represent all commonly used units (except logscale units like decibels).
Another core design choice is to eagerly convert all values to SI units when applying mathematical operations. This is both for simplicity and performance, as all dimensions only have one SI unit representation (simplicity) and no conversion factors need to occur between units (performance). This also means that if a single quantity is used in multiple calculations, a performance boost can be achieved by calling x_si = ubase(x)
and using that value, as conversions don’t need to be repeated. Results can always be converted back to desired units, and if the conversion fails due to a dimension mismatch, a helpful error is given with respect to the dimensional difference.
Another degree of flexibility is achieved in how it is unopinionated with respect to unit registries; you can easily define your own registry and use it for macros as opposed to the .UnitRegistry
module (the original UnitRegistry module is only ~30 lines of code). This can be helpful if there is a conflict in the symbolic unit representation (which can happen when applying a single solution codebase to multiple clients). Because of this, the default registry isn’t automatically exported. In order to use it, UnitRegistry must be explicitly imported in order to use the common string macros:
using FlexUnits.jl, .UnitRegistry
Finally, all Quantity
instances are generic and do not subtype into Real
or even Number
. This allows me to have generic unitful behavior on custom objects or Number types without running into ambiguities.
Major differences between FlexUnits.jl and DynamicQuantities.jl
- Fully supports affine units (like °C and °F) and can potentially support logarithmic units (like dB) in a separate registry
- The string macro
u_str
and parsing functionuparse
are not automatically exported (allowing users to export their own registries) - Easier to build your own unit registry (allowing differnet behaviour for
u_str
anduparse
) - While units are pretty-printed by default, you can enable parseable unit outputs by setting
pretty_print_units(false)
which is useful for outputting unit information in JSON - More closely resembles the Unitful.jl API in many ways
- No symbolic units (everything eagerly evaluates to SI units)
- The function
uexpand
is replaced byubase
- There is no
Quantity
type that subtypes toNumber
orReal
. AQuantity
in FlexUnits.jl is equivalent to aGenericQuantity
in DynamicQuantities.jl This is a deliberate design decision to avoid ambiguities on numerical operations with user-defined types.
Major differences between FlexUnits.jl and Unitful.jl
- Units are not specialized (u"m/s" returns the same concrete type as u"°C") which is much faster when units cannot be inferred
- The string macro
u_str
and parsing functionuparse
are not automatically exported (allowing users to export their own registries) - Units are not dynamically tracked, quantities are displayed as though
upreferred
was called on them - The function
upreferred
is replaced byubase
which converts to raw dimensions (like SI) which are not configurable - Operations on affine units do not produce errors (due to automatic conversion to dimensional form). This may may yield unituitive (but more consistent) results.
- Unit registries are much simpler; a registry is simply a dict of units, all of the same type, living inside a module. Custom registries inside user-defined modules are not neccessary, but are still supported.
Quantity
in FlexUnits.jl does not subtype toNumber
in order to support more value types (such as a Distribution or Array)