Systems of units¶
SI is the default, but not the only system a Dim can count in. With the systems
feature (in quantities), exact_units::systems adds two more: Natural (\(\hbar = c = 1\), particle physics' units,
below) and Gaussian (cm, g, s, the units of classical electromagnetism before SI, below too). Every unit this
crate ships but these two is SI's.
A dimension belongs to one system, read off Dim::system(). Two systems' values never add, convert or compare: a
unit of SI and a unit of Gaussian can't even multiply into one type.
use exact_units::{Dim, Unit, units::{Meter, Times}};
use exact_units::systems::gaussian::Gauss;
const D: Dim<'static> = <Times<Meter, Gauss> as Unit>::DIM;
A value crosses between SI and another system only by the named step Fixed::to_system::<S>(), finished by
convert, convert_rounded, convert_with::<Set, ..> or convert_rounded_with into a
unit of S. The target unit's own quantity says what the value becomes there: the electron's rest energy, carried
in Natural's one unit, becomes a mass if the far side names Gram, or an energy if it names Joule:
use exact_units::{Fixed, Rounding, prefix::{Kilo, Mega}, units::Gram};
use exact_units::systems::{Natural, Si, natural::NaturalElectronVolt};
// the electron's rest energy, a mass by the exact factor e/c²
let m_e = Fixed::<NaturalElectronVolt, Mega, f64>::try_new(0.51099895).unwrap();
let kg = m_e.to_system::<Si>().convert_rounded::<Gram, Kilo, f64>(Rounding::NearestEven).unwrap();
assert!((kg.get() / 9.1093837e-31 - 1.0).abs() < 1e-8);
// and back, read as the mass-in-eV it started as
let back = kg.to_system::<Natural>().convert_rounded::<NaturalElectronVolt, Mega, f64>(Rounding::NearestEven).unwrap();
assert!((back.get() - 0.51099895).abs() < 1e-7);
A value crosses by one of two ways, and a row always wins over the rule:
- A row (
BridgeDef), one per quantity the system's table lists: a fixed factor between one coherent unit of that quantity in each system. Gaussian needs this for every one of its electromagnetic quantities, because its dimension alone can't tell two of them apart. - The rule by dimension (
BaseRule), else: each SI base maps onto a product of the other system's bases with an exact factor, and a dimension's factor is the product of its bases' to their exponents. This is how every mechanical unit crosses, with no quantity named at all.
Where neither gives an answer, crossing is a compile error that names the reason: see What never crosses below.
Natural units: \(\hbar = c = 1\)¶
systems::Natural's bases are eV, A, K, mol, cd: mass, energy, momentum and inverse length, which SI keeps apart,
are all eV¹. The ampere, kelvin, mole and candela stay bases of their own, so an electromagnetic unit still needs
one. Since the 2019 SI redefinition \(\hbar\) (\(= h/2\pi\)), \(c\) and \(e\) are exact, so every entry of Natural's rule is an exact
Magnitude, \(\pi\) included, and no set of constants is ever needed to cross into or out of it:
- \(1\,\unit{s} = (e/\hbar)\,\unit{eV}^{-1}\)
- \(1\,\unit{m} = (e/(\hbar c))\,\unit{eV}^{-1}\)
- \(1\,\unit{kg} = (c^2/e)\,\unit{eV}\)
Natural's only unit is natural::NaturalElectronVolt (symbol eV, prefixes decimal): not
units::ElectronVolt, SI's fixed \(1.602176634 \cdot 10^{-19}\,\unit{J}\), but the coherent unit of Natural
itself, which stands for a mass, an energy, a momentum or an inverse length depending on what the far side names:
use exact_units::{Fixed, Rounding, prefix::{Mega, Peta}, units::Wavenumber};
use exact_units::systems::{Natural, Si, natural::NaturalElectronVolt};
// 1/fm as an energy, by ħc: 197.3269804… MeV
let k = Fixed::<Wavenumber, Peta, f64>::try_new(1.0).unwrap();
let e = k.to_system::<Natural>().convert_rounded::<NaturalElectronVolt, Mega, f64>(Rounding::NearestEven).unwrap();
assert!((e.get() - 197.3269804).abs() < 1e-7);
// and back, read as the inverse length it started as
let back = e.to_system::<Si>().convert_rounded::<Wavenumber, Peta, f64>(Rounding::NearestEven).unwrap();
assert!((back.get() - 1.0).abs() < 1e-12);
A frequency never crosses, even in Natural. \(E = hf\) and \(E = \hbar\omega\) differ by a factor of \(2\pi\), so Hertz, and any
other anonymous s⁻¹, is listed in Natural's no_rule and refuses the rule outright; Frequency, Activity and
SymbolRate (Becquerel's and Baud's kinds) are refused with it, as they are s⁻¹ too. An angular
frequency keeps its own radian instead of losing it by accident: rad/s crosses to rad·eV by \(\hbar\), exactly, and only an
explicit strip turns that into a plain eV.
use exact_units::{Dim, MarkerId, Unit, units::{Per, Radian, Second, Times}};
use exact_units::systems::natural::NaturalElectronVolt;
type Omega = Per<Radian, Second>;
type RadEv = Times<Radian, NaturalElectronVolt>;
assert_ne!(RadEv::DIM, NaturalElectronVolt::DIM);
assert_eq!(RadEv::DIM.strip(MarkerId::RADIAN), NaturalElectronVolt::DIM);
use exact_units::{Fixed, prefix::One, units::Hertz};
use exact_units::systems::{Natural, natural::NaturalElectronVolt};
let f = Fixed::<Hertz, One, i32>::new(50);
let _ = f.to_system::<Natural>().convert::<NaturalElectronVolt, One, i32>();
Dimension one is wider here. \(E \cdot L\), \(m \cdot L\) and an action (\(\hbar\) itself, \(\unit{J} \cdot \unit{s}\)) are all dimension one in Natural, so the
quantity rule "a dimension-one factor keeps the other side's quantity" (Quantities)
fires on more products than it does in SI. This names nothing by accident: no quantity of
quantities has a Natural dimension, so every quantity Natural products ever get is either
Derived of eV or a RatioOf it — never a kind SI never declared.
Gaussian units: cm, g, s¶
systems::Gaussian's bases are g, cm, s (plus K, mol, cd): the centimetre-gram-second system, with no current of
its own. Its electric quantities follow the electrostatic system of units (ESU) and its magnetic quantities the
electromagnetic one (EMU), and that choice is what makes Gaussian the one system this crate can't build from a
projection matrix alone.
\(E\), \(D\), \(P\) (electric field, displacement, polarization) and \(B\), \(H\), \(M\) (flux density, field strength, magnetization) all
come out to the same Gaussian dimension, \(\unit{g}^{1/2} \cdot \unit{cm}^{-1/2} \cdot \unit{s}^{-1}\), though their SI dimensions differ (\(D\) and \(P\) are C/m²,
\(H\) and \(M\) are A/m). And within one pair, the two quantities differ by exactly \(4\pi\) (rationalisation) even though they
share both a Gaussian and an SI dimension — no function of the dimension can tell \(D\) from \(P\), or \(H\) from \(M\). So
Gaussian's crossing can't be a formula over exponents: it is a table, one row per quantity
(system::BridgeDef), after Jackson's \(B_{\mathrm{G}} = B \cdot \sqrt{4\pi/\mu_0}\) and \(q_{\mathrm{G}} = q/\sqrt{4\pi\varepsilon_0}\). Every row is written over
a named const in systems::gaussian, exact under every set of measured constants and
exactly 1 under Ampere1948, where the customary table comes back:
| Quantity | 1 Gaussian unit | in SI |
|---|---|---|
| charge | 1 statC | \(1/(10 c S)\,\unit{C}\) |
| current | 1 statA | \(1/(10 c S)\,\unit{A}\) |
| potential | 1 statV | \(10^{-6} c S\,\unit{V}\) |
| electric field, \(E\) | 1 statV/cm | \(10^{-4} c S\,\unit{V/m}\) |
| displacement, \(D\) | 1 statV/cm | \(10^3/(4\pi c S)\,\unit{C/m^2}\) |
| polarization, \(P\) | 1 statC/cm² | \(10^3/(c S)\,\unit{C/m^2}\) |
| flux density, \(B\) | 1 G | \(10^{-4} S\,\unit{T}\) |
| field strength, \(H\) | 1 Oe | \(10^3/(4\pi S)\,\unit{A/m}\), \(= (1\,\mathrm{G})/\mu_0\) |
| magnetization, \(M\) | 1 emu/cm³ | \(10^3/S\,\unit{A/m}\) |
| flux | 1 Mx | \(10^{-8} S\,\unit{Wb}\) |
| resistance, inductance | 1 statΩ, 1 statH | \(10^{-5} c^2 S^2\,\unit{Ω}\), \(\unit{H}\) |
| capacitance | 1 cm | \(1/(10^{-5} c^2 S^2)\,\unit{F}\) |
This crate's units name six of those rows: gaussian::{Statcoulomb, Statampere, Statvolt, Gauss, Oersted,
Maxwell} (charge, current, potential, \(B\) and \(H\), and flux); \(D\), \(P\), \(M\), resistance, inductance and capacitance have a
row and a quantity but no unit marker yet, as nothing has needed one. The mechanical units — Erg, Dyne, Gal,
GaussianSecond, GaussianCentimeter, GaussianGram — need no row at all: a dimension with no electromagnetic
quantity in it crosses by the rule, \(1\,\unit{m} = 100\,\unit{cm}\) and \(1\,\unit{kg} = 1000\,\unit{g}\), as plain and exact as Natural's.
use exact_units::{Fixed, prefix::{Kilo, One}, units::{Joule, Newton}};
use exact_units::systems::{Gaussian, gaussian::{Dyne, Erg}};
// mechanical quantities cross by the rule: 1 J is 10⁷ erg, 1 N is 10⁵ dyn
let e = Fixed::<Joule, One, i64>::new(3);
assert_eq!(e.to_system::<Gaussian>().convert::<Erg, Kilo, i64>(), Some(Fixed::new(30_000)));
let f = Fixed::<Newton, One, i64>::new(2);
assert_eq!(f.to_system::<Gaussian>().convert::<Dyne, One, i64>(), Some(Fixed::new(200_000)));
A row's factor over S leaves \(\mu_0\) in the type: without a set convert has no number for it, and the conversion
doesn't compile, floats included.
use exact_units::{Fixed, prefix::One, units::MagneticFieldStrength};
use exact_units::systems::{Si, gaussian::Oersted};
let _ = Fixed::<Oersted, One, f64>::try_new(1.0).unwrap().to_system::<Si>().convert::<MagneticFieldStrength, One, f64>();
convert_with::<Set, ..> resolves it, under either CODATA edition or the 1948 convention:
use exact_units::{Fixed, Rounding, prefix::{Micro, Milli, One}, units::{MagneticFieldStrength, Tesla}};
use exact_units::constants::{Ampere1948, Codata2022};
use exact_units::systems::{Si, gaussian::{Gauss, Oersted}};
// 1 G is 10⁻⁴·S T: exactly 10⁻⁴ T under the 1948 ampere, a touch less under CODATA 2022
let b = Fixed::<Gauss, One, i32>::new(10_000);
assert_eq!(b.to_system::<Si>().convert_with::<Ampere1948, Tesla, One, i32>(), Some(Fixed::new(1)));
// 1 Oe is 1000/(4π) A/m under the 1948 ampere, and the same to the µA/m under CODATA 2022
let h = Fixed::<Oersted, Milli, i32>::new(1000);
let a48 = h.to_system::<Si>().convert_rounded_with::<Ampere1948, MagneticFieldStrength, Micro, i64>(Rounding::NearestEven);
assert_eq!(a48, Some(Fixed::new(79_577_472)));
let a22 = h.to_system::<Si>().convert_rounded_with::<Codata2022, MagneticFieldStrength, Micro, i64>(Rounding::NearestEven);
assert_eq!(a22, Some(Fixed::new(79_577_472)));
\(B\) and \(H\) are two kinds of the one dimension here, as \(D\)/\(P\) and the pairs above are, so neither adds to the other even where the numbers agree in vacuum:
use exact_units::{Fixed, prefix::One};
use exact_units::systems::gaussian::{Gauss, Oersted};
let _ = Fixed::<Gauss, One, i32>::new(1) + Fixed::<Oersted, One, i32>::new(1).convert::<Gauss, One, i32>().unwrap();
A product of rows doesn't take the rule either, so an unnamed \(E \times H\) never crosses by itself, in either
direction: Gaussian's Poynting vector is \((c/4\pi) \cdot E \times H\), not SI's \(E \times H\), so letting the rule silently cross a product
would be silently wrong. Convert the product to a named unit within its own system first (Watt, since \(P = V \cdot I\)
holds the same way in both systems), or name the quantity on the far side explicitly.
use exact_units::{Fixed, prefix::One, units::{Ampere, Meter, Per, Times, Volt}};
use exact_units::systems::gaussian::{Erg, GaussianCentimeter, GaussianSecond};
// SI's E×H, W/m²: a product of units that themselves cross only by rows
type Eh = Times<Per<Volt, Meter>, Per<Ampere, Meter>>;
// Gaussian's own flux density, erg/(s·cm²): not the same thing by a factor of 4π/c
type Flux = Per<Erg, Times<GaussianSecond, Times<GaussianCentimeter, GaussianCentimeter>>>;
let _ = Fixed::<Eh, One, f64>::try_new(1.0).unwrap()
.to_system::<exact_units::systems::Gaussian>().convert::<Flux, One, f64>();
What never crosses, and why¶
Where neither a row nor the rule applies, crossing is a compile error, and the error names which of these it is
(system::BridgeError):
- An SI base the system has no image of. Gaussian has no current: a dimension with the ampere in it, and no
row for its quantity, stops the rule (
Unmapped). - A quantity the system's rule refuses by name. Natural's
no_rulelistsFrequency,ActivityandSymbolRate, so even a mechanical-looking s⁻¹ with no electromagnetic base in it still refuses (NoRule). - A kind the rule can't name. The rule only ever lands on a dimension's open root (
Derived); a value whose quantity is a kind of its own, on either side, needs a row or an explicit step first (Kind). - A product of units that cross only by rows. Named leaves that are themselves rows (Gaussian's \(B\), \(H\), ..) make
the whole product refuse the rule, so it crosses only once a leaf is converted within its system or the whole is
named (
ProductOfRows).
Distinguished bases pass every bridge untouched. rad, Np, bit and a crate's own counted base (Distinguished bases) are the same in every system, so a torque in J/rad crosses into Gaussian with its radian intact, at the ordinary mechanical factor:
use exact_units::{Fixed, prefix::{Milli, One}, units::{Per, Radian, Torque}};
use exact_units::systems::{Gaussian, gaussian::Erg};
type ErgPerRad = Per<Erg, Radian>;
let t = Fixed::<Torque, Milli, i64>::new(3);
assert_eq!(t.to_system::<Gaussian>().convert::<ErgPerRad, One, i64>(), Some(Fixed::new(30_000)));
Declaring your own system¶
unit! and quantity! lines name a system before the bracket to join it instead of SI's:
[Gaussian: g 1/2, cm -1/2, s -1] looks each base symbol up in that system's own list at compile time, a symbol
the system doesn't have is a compile error naming the system. System is a plain trait
(const ID, const BASES, const DEF: &'static SystemDef<'static>), so a crate's own system joins the same way
this crate's three do: see unit!'s dimension for the exact syntax.
Not through q!¶
q!'s symbol table is built from units::ALL, SI's own units, so Gaussian's and Natural's units never reach it:
q!(1 G) would collide with the SI prefix giga, and eV and g already name SI's own electronvolt and gram.
Write the typed unit directly (Fixed::<Gauss, One, i32>::new(1)), or cross into the system with to_system from
an SI value q! can name.