danwi 0.4.2

Zero-cost dimensional analysis library with SI units, compile-time checking, and no_std support
Documentation
#![allow(non_upper_case_globals)]

//! Units as zero-sized marker types.
//!
//! A unit is a type implementing [`UnitDef`]: its dimension, its conversion
//! to the SI base unit as a compile-time rational scale/offset, and its
//! symbol. [`Unit`] is the value-level handle; constants like `kV` are
//! `Unit<Kilo<Volt>>`. Units are defined with the [`units!`](crate::units)
//! macro; SI prefixes are the generic wrappers in [`prefix`].

use core::marker::PhantomData;

use crate::dimension::Dimensions;

#[macro_use]
mod macros;
mod ops;

pub mod prefix;
pub mod si;

/// A unit definition: dimension, conversion to the SI base unit, symbol.
///
/// A value `x_unit` measured in this unit corresponds to
///
/// ```text
/// x_base = (SCALE_NUM / SCALE_DEN) * x_unit + (OFFSET_NUM / OFFSET_DEN)
/// ```
///
/// Coherent SI units have `SCALE = 1/1` and `OFFSET = 0/1`, prefixed units
/// scale by the power of ten, non-decimal units (inch, minute) use an
/// arbitrary rational, and affine units (Celsius) add an offset. Everything
/// is an associated const, so conversions fold at compile time. Implement
/// via the [`units!`](crate::units) macro rather than by hand.
pub trait UnitDef {
    /// The dimension measured by this unit.
    type Dim: Dimensions;

    const SCALE_NUM: i128;
    const SCALE_DEN: i128;
    const OFFSET_NUM: i128;
    const OFFSET_DEN: i128;

    /// SI prefix symbol, e.g. `"k"`. Empty for unprefixed units.
    const PREFIX: &'static str = "";
    /// Unit symbol without prefix, e.g. `"V"` or `"°C"`.
    const SYMBOL: &'static str;

    // Evaluated by `Unit::new`, so an invalid hand-written impl fails at
    // compile time instead of producing inf/NaN conversions.
    #[doc(hidden)]
    const VALID: () = assert!(
        Self::SCALE_NUM > 0 && Self::SCALE_DEN > 0 && Self::OFFSET_DEN != 0,
        "invalid unit: scale must be positive and offset denominator non-zero",
    );
}

/// Value-level handle for a [`UnitDef`] marker.
///
/// Zero-sized: passing units by value (`5.0 * kV`, `q.to(mV)`) costs
/// nothing. It also anchors the crate's blanket operator impls, which is
/// what lets units defined downstream use the same operators.
pub struct Unit<M> {
    _phantom: PhantomData<M>,
}

impl<M: UnitDef> Unit<M> {
    #[inline]
    pub const fn new() -> Self {
        let () = M::VALID;
        Self {
            _phantom: PhantomData,
        }
    }
}

impl<M: UnitDef> Default for Unit<M> {
    fn default() -> Self {
        Self::new()
    }
}

// Manual `Copy`/`Clone`/`Debug`: the derives would add bounds on `M`,
// which is only carried as `PhantomData`.
impl<M> Clone for Unit<M> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<M> Copy for Unit<M> {}

impl<M: UnitDef> core::fmt::Debug for Unit<M> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "Unit({}{})", M::PREFIX, M::SYMBOL)
    }
}

/// Greatest common divisor of two positive integers.
const fn gcd(mut a: i128, mut b: i128) -> i128 {
    while b != 0 {
        let r = a % b;
        a = b;
        b = r;
    }
    a
}

// Prefix scales compose by multiplication (`Kilo<Gram>` is `1000 * 1/1000`)
// and must be reduced to lowest terms, or `kg` would compile to
// `x * 1000.0 / 1000.0` instead of folding away.

/// Numerator of `n/d` reduced to lowest terms.
pub const fn reduce_num(n: i128, d: i128) -> i128 {
    assert!(n > 0 && d > 0, "unit scale must be positive");
    n / gcd(n, d)
}

/// Denominator of `n/d` reduced to lowest terms.
pub const fn reduce_den(n: i128, d: i128) -> i128 {
    assert!(n > 0 && d > 0, "unit scale must be positive");
    d / gcd(n, d)
}