Skip to content

Instantly share code, notes, and snippets.

@forrestthewoods
Created May 20, 2024 20:41
Show Gist options
  • Save forrestthewoods/486ec3b6cad8c0f7dad2e33183747ce1 to your computer and use it in GitHub Desktop.
Save forrestthewoods/486ec3b6cad8c0f7dad2e33183747ce1 to your computer and use it in GitHub Desktop.
// ---------------- quantity.rs
/*!
Declares foundational `QuantityT<T,U>` struct upon which fts_units is built.
`QuantityT<T,U>` defines a `Quantity` which stores an `amount` of type `T` with units type `U`. Mathematical operations with `QuantityT<T,U>` values produces new types of `QuantityT` with a different `U` type.
If `U` is zero sized then all the type checking done and compile time and boils down to nothing. No run-time overhead for either CPU or memory.
Mathematical operations for `QuantityT<T,U>` are defined so long as they are independently defined for both `T` and `U`.
*/
use std::fmt;
use std::marker::PhantomData;
use crate::ops::*;
// --------------------------------
// Trait declarations
// --------------------------------
/// Must be implemented for `T` in `QuantityT<T,U>`.
pub trait Amount: Copy + Clone {}
/// Helper for performing generic computations with `QuantityT<T,U>` types.
pub trait Quantity {
type AmountType: Amount;
type UnitsType;
fn new(amount: Self::AmountType) -> Self;
fn amount(&self) -> Self::AmountType;
}
/// Helper for converting units. For example from Meters to Kilometers.
///
/// ```rust
/// # use fts_units::si_system::quantities::f32::*;
/// # use fts_units::quantity::ConvertUnits;
/// let d = Kilometers::new(15.3);
/// let t = Hours::new(2.7);
/// let kph : KilometersPerHour = d / t;
///
/// let mps : MetersPerSecond = kph.convert_into();
/// let mps = MetersPerSecond::convert_from(kph);
/// ```
pub trait ConvertUnits<T> {
fn convert_from(v: T) -> Self;
fn convert_into(self) -> T;
}
/// Helpers for casting `T` in `QuantityT<T,U>`.
///
/// ```rust
/// # use fts_units::si_system::quantities::*;
/// # use fts_units::quantity::{CastAmount, Quantity};
/// let m = Meters::<f32>::new(7.73);
/// let i : Meters<i32> = m.cast_into();
/// assert_eq!(i.amount(), 7);
/// ```
pub trait CastAmount<T> {
fn cast_from(v: T) -> Self;
fn cast_into(self) -> T;
}
// --------------------------------
// Struct declarations
// --------------------------------
/// Foundational struct used by all run-time quantities.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct QuantityT<T, U>
where
T: Amount,
{
amount: T,
_u: PhantomData<U>,
}
// --------------------------------
// QuantityT impls
// --------------------------------
impl<T, U> QuantityT<T, U>
where
T: Amount,
{
pub fn new(amount: T) -> QuantityT<T, U> {
QuantityT {
amount: amount,
_u: PhantomData,
}
}
}
impl<T, U> Quantity for QuantityT<T, U>
where
T: Amount,
{
type AmountType = T;
type UnitsType = U;
fn new(amount: T) -> Self {
QuantityT::<T, U>::new(amount)
}
fn amount(&self) -> Self::AmountType {
self.amount
}
}
impl<T, Q> std::ops::Add<Self> for QuantityT<T, Q>
where
T: Amount + std::ops::Add<T>,
Q: std::ops::Add<Q>,
AddOutput<T, T>: Amount,
{
type Output = QuantityT<AddOutput<T, T>, AddOutput<Q, Q>>;
fn add(self, other: Self) -> Self::Output {
Self::Output::new(self.amount + other.amount)
}
}
impl<T, Q> std::ops::Sub<Self> for QuantityT<T, Q>
where
T: Amount + std::ops::Sub<T>,
Q: std::ops::Sub<Q>,
SubOutput<T, T>: Amount,
{
type Output = QuantityT<SubOutput<T, T>, SubOutput<Q, Q>>;
fn sub(self, other: Self) -> Self::Output {
Self::Output::new(self.amount - other.amount)
}
}
impl<T, Q0, Q1> std::ops::Mul<QuantityT<T, Q1>> for QuantityT<T, Q0>
where
T: Amount + std::ops::Mul<T>,
Q0: std::ops::Mul<Q1>,
MulOutput<T, T>: Amount,
{
type Output = QuantityT<MulOutput<T, T>, MulOutput<Q0, Q1>>;
fn mul(self, other: QuantityT<T, Q1>) -> Self::Output {
Self::Output::new(self.amount * other.amount)
}
}
impl<T, Q0, Q1> std::ops::Div<QuantityT<T, Q1>> for QuantityT<T, Q0>
where
T: Amount + std::ops::Div<T>,
Q0: std::ops::Div<Q1>,
DivOutput<T, T>: Amount,
{
type Output = QuantityT<DivOutput<T, T>, DivOutput<Q0, Q1>>;
fn div(self, other: QuantityT<T, Q1>) -> Self::Output {
Self::Output::new(self.amount / other.amount)
}
}
impl<T, U> std::fmt::Display for QuantityT<T, U>
where
T: std::fmt::Display + Amount,
U: std::fmt::Display + Default,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.amount, U::default())
}
}
impl<T, U> Invert for QuantityT<T, U>
where
T: Invert + Amount,
U: Invert,
InvertOutput<T>: Amount,
{
type Output = QuantityT<InvertOutput<T>, InvertOutput<U>>;
fn invert(self) -> Self::Output {
Self::Output::new(self.amount.invert())
}
}
impl<T, U> Sqrt for QuantityT<T, U>
where
T: num_traits::float::Float + Amount,
U: Sqrt,
{
type Output = QuantityT<T, SqrtOutput<U>>;
fn sqrt(self) -> Self::Output {
Self::Output::new(self.amount.sqrt())
}
}
impl<T, U> std::convert::From<T> for QuantityT<T, U>
where
T: Amount,
{
fn from(value: T) -> Self {
Self::new(value)
}
}
impl Amount for i8 {}
impl Amount for i16 {}
impl Amount for i32 {}
impl Amount for i64 {}
impl Amount for i128 {}
impl Amount for u8 {}
impl Amount for u16 {}
impl Amount for u32 {}
impl Amount for u64 {}
impl Amount for u128 {}
impl Amount for f32 {}
impl Amount for f64 {}
// ------------------- ratio.rs
/*!
Storage and operations for compile-type Ratio types.
*/
use std::marker::PhantomData;
use typenum::consts::*;
use typenum::int::{NInt, PInt};
use typenum::{Integer, NonZero};
use crate::ops::*;
// --------------------------------
// Trait declarations
// --------------------------------
/// Helper to faciliate working with Ratio types generically.
pub trait Ratio {
fn numerator() -> i64;
fn denominator() -> i64;
}
// --------------------------------
// Struct declarations
// --------------------------------
/// Special type to represent a Zero Ratio.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct RatioZero;
/// Ratio type with positive, non-zero numerator and denominator.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct RatioT<NUM, DEN>
where
NUM: Integer + NonZero,
DEN: Integer + NonZero,
{
_num: PhantomData<NUM>,
_den: PhantomData<DEN>,
}
// --------------------------------
// Ratio impls
// --------------------------------
impl<NUM, DEN> Ratio for RatioT<NUM, DEN>
where
NUM: Integer + NonZero,
DEN: Integer + NonZero,
{
fn numerator() -> i64 {
NUM::I64
}
fn denominator() -> i64 {
DEN::I64
}
}
impl<N, D> NonZero for RatioT<N, D>
where
N: Integer + NonZero,
D: Integer + NonZero,
{
}
impl Ratio for RatioZero {
fn numerator() -> i64 {
0
}
fn denominator() -> i64 {
0
}
}
impl Xor<RatioZero> for RatioZero
where
RatioZero: Default,
{
type Output = RatioZero;
fn xor(self, _other: RatioZero) -> Self::Output {
Default::default()
}
}
impl<N, D> Xor<RatioT<N, D>> for RatioT<N, D>
where
N: Integer + NonZero,
D: Integer + NonZero,
RatioT<N, D>: Default,
{
type Output = RatioT<N, D>;
fn xor(self, _other: RatioT<N, D>) -> Self::Output {
Default::default()
}
}
impl<N, D> Xor<RatioZero> for RatioT<N, D>
where
N: Integer + NonZero,
D: Integer + NonZero,
RatioT<N, D>: Default,
{
type Output = RatioT<N, D>;
fn xor(self, _other: RatioZero) -> Self::Output {
Default::default()
}
}
impl<N, D> Xor<RatioT<N, D>> for RatioZero
where
N: Integer + NonZero,
D: Integer + NonZero,
RatioT<N, D>: Default,
{
type Output = RatioT<N, D>;
fn xor(self, _other: RatioT<N, D>) -> Self::Output {
Default::default()
}
}
impl<E> ReduceWith<E> for RatioZero {
type Output = RatioZero;
fn reduce_with(self, _other: E) -> Self::Output {
Default::default()
}
}
impl<N, D> ReduceWith<Z0> for RatioT<N, D>
where
Self: Default,
N: Integer + NonZero,
D: Integer + NonZero,
{
type Output = RatioZero;
fn reduce_with(self, _other: Z0) -> Self::Output {
Default::default()
}
}
impl<N, D, U> ReduceWith<PInt<U>> for RatioT<N, D>
where
Self: Default,
N: Integer + NonZero,
D: Integer + NonZero,
U: typenum::Unsigned + NonZero,
{
type Output = Self;
fn reduce_with(self, _other: PInt<U>) -> Self::Output {
Default::default()
}
}
impl<N, D, U> ReduceWith<NInt<U>> for RatioT<N, D>
where
Self: Default,
N: Integer + NonZero,
D: Integer + NonZero,
U: typenum::Unsigned + NonZero,
{
type Output = Self;
fn reduce_with(self, _other: NInt<U>) -> Self::Output {
Default::default()
}
}
// --------------- ops.rs
//! Traits and helpers to faciliate working with generic Quantities.
// --------------------------------
// Traits
// --------------------------------
/// Helper to reduce Ratios to RatioZero when Exponent becomes zero
pub trait Reduce {
type Output;
fn reduce(self) -> Self::Output;
}
/// Helper to reduce Ratios to RatioZero when Exponent becomes zero
pub trait ReduceWith<T> {
type Output;
fn reduce_with(self, other: T) -> Self::Output;
}
/// Provide generic Invert interface for invertable types (such as floats)
pub trait Invert {
type Output;
fn invert(self) -> Self::Output;
}
/// Provide generic Sqrt interface for supported types (such as floats)
pub trait Sqrt {
type Output;
fn sqrt(self) -> Self::Output;
}
/// Used only at the type level for Xor'ing Zero and Non-Zero Ratios
pub trait Xor<T> {
type Output;
fn xor(self, other: T) -> Self::Output;
}
// --------------------------------
// Utilities
// --------------------------------
pub type AddOutput<T, U> = <T as std::ops::Add<U>>::Output;
pub type SubOutput<T, U> = <T as std::ops::Sub<U>>::Output;
pub type MulOutput<T, U> = <T as std::ops::Mul<U>>::Output;
pub type DivOutput<T, U> = <T as std::ops::Div<U>>::Output;
pub type XorOutput<T, U> = <T as Xor<U>>::Output;
pub type MulOutput3<T, U, V> = MulOutput<MulOutput<T, U>, V>;
pub type MulOutput4<T, U, V, W> = MulOutput<MulOutput<MulOutput<T, U>, V>, W>;
pub type InvertOutput<T> = <T as Invert>::Output;
pub type SqrtOutput<T> = <T as Sqrt>::Output;
pub type ReduceOutput<T> = <T as Reduce>::Output;
pub type ReduceWithOutput<T, U> = <T as ReduceWith<U>>::Output;
// --------------------------------
// Impls for built-in types
// --------------------------------
impl Invert for f32 {
type Output = f32;
fn invert(self) -> Self::Output {
1.0 / self
}
}
impl Invert for f64 {
type Output = f64;
fn invert(self) -> Self::Output {
1.0 / self
}
}
// ----------- si_system.rs
/*!
Structs and traits to enable SI Unit quantities.
There are three core structs with matching traits.
```no_compile
pub struct SIUnitsT<RATIOS,EXPONENTS> { ... }
pub struct SIRatiosT<LENGTH, MASS, TIME> { ... }
pub struct SIExponentsT<LENGTH, MASS, TIME> { ... }
pub trait SIUnits { type Ratios: SIRatios; type Exponents: SIExponents; }
pub trait SIRatios { type Length; type Mass; type Time; }
pub trait SIExponents { type Length; type Mass; type Time; }
```
`SIUnitsT` has `RATIOS` and `EXPONENTS`. Traits such as `std::ops::Add` are implemented for `SIUnitsT<R,E>` when `R` and `E` are the same. `std::ops::mul` is implemented for any pair of exponents, but with an `Xor` restriction on `R`.
*/
use std::fmt;
use std::marker::PhantomData;
use typenum::consts::*;
use typenum::{Diff, Integer, NonZero, PartialDiv, Sum};
use crate::ops::*;
use crate::quantity::*;
use crate::ratio::*;
use crate::typenum_ext::*;
pub use self::ratios::*;
// --------------------------------
// SI System Traits
// --------------------------------
/// Helper to faciliate generic operations on `SIUnitsT<R,E>`.
pub trait SIUnits {
type Ratios: SIRatios;
type Exponents: SIExponents;
}
/// Helper to faciliate generic operations on `SIRatiosT<L,M,T>`.
pub trait SIRatios {
type Length: Ratio;
type Mass: Ratio;
type Time: Ratio;
}
/// Helper to faciliate generic operations on `SIExponentsT<L,M,T>`.
pub trait SIExponents {
type Length: Integer;
type Mass: Integer;
type Time: Integer;
}
// --------------------------------
// SI System Structs
// --------------------------------
/// Foundational type of `si_system`. Holds all Ratios and Exponents as types.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct SIUnitsT<RATIOS, EXPONENTS>
where
RATIOS: SIRatios,
EXPONENTS: SIExponents,
{
_r: PhantomData<RATIOS>,
_e: PhantomData<EXPONENTS>,
}
/// Holds type Ratios for each `si_system` dimension.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct SIRatiosT<L, M, T>
where
L: Ratio,
M: Ratio,
T: Ratio,
{
_l: PhantomData<L>,
_m: PhantomData<M>,
_t: PhantomData<T>,
}
/// Holds type exponents for each `si_system` dimension.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct SIExponentsT<L, M, T>
where
L: Integer,
M: Integer,
T: Integer,
{
_l: PhantomData<L>,
_m: PhantomData<M>,
_t: PhantomData<T>,
}
/// Internal type used for `SIUnitsT` operations.
#[doc(hidden)]
#[derive(Default)]
pub struct RatioExponentT<R: Ratio, E: Integer> {
_r: PhantomData<R>,
_e: PhantomData<E>,
}
/// Internal type used for `SIUnitsT` operations.
#[doc(hidden)]
#[derive(Copy, Clone)]
pub struct AmountT<T: Amount>(T);
// --------------------------------
// impls
// --------------------------------
// Basic invert (always possible)
impl<R, E> Invert for SIUnitsT<R, E>
where
R: SIRatios,
E: SIExponents,
units::Dimensionless: std::ops::Div<Self>,
DivOutput<units::Dimensionless, Self>: Default,
{
type Output = DivOutput<units::Dimensionless, Self>;
fn invert(self) -> Self::Output {
Default::default()
}
}
// Basic SIUnitsT add
impl<R, E> std::ops::Add<SIUnitsT<R, E>> for SIUnitsT<R, E>
where
R: SIRatios,
E: SIExponents,
{
type Output = Self;
fn add(self, _other: Self) -> Self::Output {
SIUnitsT {
_r: PhantomData,
_e: PhantomData,
}
}
}
// Basic SIUnitsT subtract
impl<R, E> std::ops::Sub<SIUnitsT<R, E>> for SIUnitsT<R, E>
where
R: SIRatios,
E: SIExponents,
{
type Output = Self;
fn sub(self, _other: Self) -> Self::Output {
SIUnitsT {
_r: PhantomData,
_e: PhantomData,
}
}
}
// SIExponents Add
impl<L0, M0, T0, L1, M1, T1> std::ops::Add<SIExponentsT<L1, M1, T1>> for SIExponentsT<L0, M0, T0>
where
L0: Integer + std::ops::Add<L1>,
M0: Integer + std::ops::Add<M1>,
T0: Integer + std::ops::Add<T1>,
L1: Integer,
M1: Integer,
T1: Integer,
AddOutput<L0, L1>: Integer,
AddOutput<M0, M1>: Integer,
AddOutput<T0, T1>: Integer,
{
type Output = SIExponentsT<Sum<L0, L1>, Sum<M0, M1>, Sum<T0, T1>>;
fn add(self, _other: SIExponentsT<L1, M1, T1>) -> Self::Output {
SIExponentsT {
_l: PhantomData,
_m: PhantomData,
_t: PhantomData,
}
}
}
// SIExponents Sub
impl<L0, M0, T0, L1, M1, T1> std::ops::Sub<SIExponentsT<L1, M1, T1>> for SIExponentsT<L0, M0, T0>
where
L0: Integer + std::ops::Sub<L1>,
M0: Integer + std::ops::Sub<M1>,
T0: Integer + std::ops::Sub<T1>,
L1: Integer,
M1: Integer,
T1: Integer,
SubOutput<L0, L1>: Integer,
SubOutput<M0, M1>: Integer,
SubOutput<T0, T1>: Integer,
{
type Output = SIExponentsT<Diff<L0, L1>, Diff<M0, M1>, Diff<T0, T1>>;
fn sub(self, _other: SIExponentsT<L1, M1, T1>) -> Self::Output {
SIExponentsT {
_l: PhantomData,
_m: PhantomData,
_t: PhantomData,
}
}
}
/// Multiplying a pair of SIUnits is complicated
/// Ratios: must be XOR-able
/// meters * meters is fine.
/// meters * kilometers is not. The output type is ambiguous.
/// Exponents: can be anything. will be added together
/// meters * meters = meters squared
/// However exponents which become zero must zero out their corresponding ratio
/// 10 kilometers per hour * 2 hours = 20 kilometers. The time Ratio was hours, but needs to be zero'd out.
impl<R0, E0, R1, E1> std::ops::Mul<SIUnitsT<R1, E1>> for SIUnitsT<R0, E0>
where
R0: SIRatios + Xor<R1>,
E0: SIExponents + std::ops::Add<E1>,
R1: SIRatios,
E1: SIExponents,
XorOutput<R0, R1>: SIRatios + ReduceWith<AddOutput<E0, E1>> + Default,
AddOutput<E0, E1>: SIExponents + Default,
ReduceWithOutput<XorOutput<R0, R1>, AddOutput<E0, E1>>: SIRatios + Default,
ReduceOutput<SIUnitsT<XorOutput<R0, R1>, AddOutput<E0, E1>>>: Default,
{
type Output = ReduceOutput<SIUnitsT<XorOutput<R0, R1>, AddOutput<E0, E1>>>;
fn mul(self, _other: SIUnitsT<R1, E1>) -> Self::Output {
Default::default()
}
}
/// See coments on `std::ops::Mul<SIUnitsT<_,_>>`
impl<R0, E0, R1, E1> std::ops::Div<SIUnitsT<R1, E1>> for SIUnitsT<R0, E0>
where
R0: SIRatios + Xor<R1>,
E0: SIExponents + std::ops::Sub<E1>,
R1: SIRatios,
E1: SIExponents,
XorOutput<R0, R1>: SIRatios + ReduceWith<SubOutput<E0, E1>> + Default,
SubOutput<E0, E1>: SIExponents + Default,
ReduceWithOutput<XorOutput<R0, R1>, SubOutput<E0, E1>>: SIRatios + Default,
ReduceOutput<SIUnitsT<XorOutput<R0, R1>, SubOutput<E0, E1>>>: Default,
{
type Output = ReduceOutput<SIUnitsT<XorOutput<R0, R1>, SubOutput<E0, E1>>>;
fn div(self, _other: SIUnitsT<R1, E1>) -> Self::Output {
Default::default()
}
}
impl<L, M, T> SIRatios for SIRatiosT<L, M, T>
where
L: Ratio,
M: Ratio,
T: Ratio,
{
type Length = L;
type Mass = M;
type Time = T;
}
impl<L, M, T> SIExponents for SIExponentsT<L, M, T>
where
L: Integer,
M: Integer,
T: Integer,
{
type Length = L;
type Mass = M;
type Time = T;
}
impl<R, E> SIUnits for SIUnitsT<R, E>
where
R: SIRatios,
E: SIExponents,
{
type Ratios = R;
type Exponents = E;
}
impl<L0, M0, T0, L1, M1, T1> Xor<SIRatiosT<L1, M1, T1>> for SIRatiosT<L0, M0, T0>
where
L0: Ratio + Xor<L1>,
M0: Ratio + Xor<M1>,
T0: Ratio + Xor<T1>,
L1: Ratio,
M1: Ratio,
T1: Ratio,
XorOutput<L0, L1>: Ratio + Default,
XorOutput<M0, M1>: Ratio + Default,
XorOutput<T0, T1>: Ratio + Default,
{
type Output = SIRatiosT<XorOutput<L0, L1>, XorOutput<M0, M1>, XorOutput<T0, T1>>;
fn xor(self, _other: SIRatiosT<L1, M1, T1>) -> Self::Output {
Default::default()
}
}
impl<L0, M0, T0, L1, M1, T1> Xor<SIExponentsT<L1, M1, T1>> for SIExponentsT<L0, M0, T0>
where
L0: Integer + Xor<L1>,
M0: Integer + Xor<M1>,
T0: Integer + Xor<T1>,
L1: Integer,
M1: Integer,
T1: Integer,
XorOutput<L0, L1>: Integer + Default,
XorOutput<M0, M1>: Integer + Default,
XorOutput<T0, T1>: Integer + Default,
{
type Output = SIExponentsT<XorOutput<L0, L1>, XorOutput<M0, M1>, XorOutput<T0, T1>>;
fn xor(self, _other: SIExponentsT<L1, M1, T1>) -> Self::Output {
Default::default()
}
}
impl<R0, E0, R1, E1> Xor<SIUnitsT<R1, E1>> for SIUnitsT<R0, E0>
where
R0: SIRatios + Xor<R1>,
E0: SIExponents + Xor<E1>,
R1: SIRatios,
E1: SIExponents,
XorOutput<R0, R1>: SIRatios,
XorOutput<E0, E1>: SIExponents,
SIUnitsT<XorOutput<R0, R1>, XorOutput<E0, E1>>: Default,
{
type Output = SIUnitsT<XorOutput<R0, R1>, XorOutput<E0, E1>>;
fn xor(self, _other: SIUnitsT<R1, E1>) -> Self::Output {
Default::default()
}
}
impl<L, M, T, E> ReduceWith<E> for SIRatiosT<L, M, T>
where
L: Ratio + ReduceWith<E::Length>,
M: Ratio + ReduceWith<E::Mass>,
T: Ratio + ReduceWith<E::Time>,
E: SIExponents,
ReduceWithOutput<L, E::Length>: Ratio + Default,
ReduceWithOutput<M, E::Mass>: Ratio + Default,
ReduceWithOutput<T, E::Time>: Ratio + Default,
{
type Output = SIRatiosT<
ReduceWithOutput<L, E::Length>,
ReduceWithOutput<M, E::Mass>,
ReduceWithOutput<T, E::Time>,
>;
fn reduce_with(self, _other: E) -> Self::Output {
Default::default()
}
}
impl<R, E> Reduce for SIUnitsT<R, E>
where
R: SIRatios + ReduceWith<E>,
E: SIExponents,
ReduceWithOutput<R, E>: SIRatios,
SIUnitsT<ReduceWithOutput<R, E>, E>: Default,
{
type Output = SIUnitsT<ReduceWithOutput<R, E>, E>;
fn reduce(self) -> Self::Output {
Default::default()
}
}
impl<T, R, E> std::ops::Mul<SIUnitsT<R, E>> for AmountT<T>
where
T: Amount,
AmountT<T>: std::ops::Mul<RatioExponentT<R::Length, E::Length>, Output = Self>
+ std::ops::Mul<RatioExponentT<R::Mass, E::Mass>, Output = Self>
+ std::ops::Mul<RatioExponentT<R::Time, E::Time>, Output = Self>,
R: SIRatios,
E: SIExponents,
{
type Output = Self;
fn mul(self, _other: SIUnitsT<R, E>) -> Self::Output {
let mut result = self;
result = result
* RatioExponentT::<R::Length, E::Length> {
_r: PhantomData,
_e: PhantomData,
};
result = result
* RatioExponentT::<R::Mass, E::Mass> {
_r: PhantomData,
_e: PhantomData,
};
result = result
* RatioExponentT::<R::Time, E::Time> {
_r: PhantomData,
_e: PhantomData,
};
result
}
}
impl<T, R, E> std::ops::Div<SIUnitsT<R, E>> for AmountT<T>
where
T: Amount,
AmountT<T>: std::ops::Div<RatioExponentT<R::Length, E::Length>, Output = Self>
+ std::ops::Div<RatioExponentT<R::Mass, E::Mass>, Output = Self>
+ std::ops::Div<RatioExponentT<R::Time, E::Time>, Output = Self>,
R: SIRatios,
E: SIExponents,
{
type Output = Self;
fn div(self, _other: SIUnitsT<R, E>) -> Self::Output {
let mut result = self;
result = result
/ RatioExponentT::<R::Length, E::Length> {
_r: PhantomData,
_e: PhantomData,
};
result = result
/ RatioExponentT::<R::Mass, E::Mass> {
_r: PhantomData,
_e: PhantomData,
};
result = result
/ RatioExponentT::<R::Time, E::Time> {
_r: PhantomData,
_e: PhantomData,
};
result
}
}
impl<T, R1, R2, E> ConvertUnits<QuantityT<T, SIUnitsT<R1, E>>> for QuantityT<T, SIUnitsT<R2, E>>
where
T: Amount,
AmountT<T>: std::ops::Mul<SIUnitsT<R1, E>, Output = AmountT<T>>
+ std::ops::Div<SIUnitsT<R1, E>, Output = AmountT<T>>
+ std::ops::Mul<SIUnitsT<R2, E>, Output = AmountT<T>>
+ std::ops::Div<SIUnitsT<R2, E>, Output = AmountT<T>>,
R1: SIRatios,
R2: SIRatios,
E: SIExponents,
{
fn convert_from(other: QuantityT<T, SIUnitsT<R1, E>>) -> Self {
let amount = AmountT(other.amount());
let r1: SIUnitsT<R1, E> = SIUnitsT {
_r: PhantomData,
_e: PhantomData,
};
let r2: SIUnitsT<R2, E> = SIUnitsT {
_r: PhantomData,
_e: PhantomData,
};
let result = amount / r2 * r1;
Self::new(result.0)
}
fn convert_into(self) -> QuantityT<T, SIUnitsT<R1, E>> {
QuantityT::<T, SIUnitsT<R1, E>>::convert_from(self)
}
}
impl<T1, T2, U> CastAmount<QuantityT<T2, U>> for QuantityT<T1, U>
where
T1: Amount + num_traits::NumCast,
T2: Amount + num_traits::NumCast,
{
fn cast_from(other: QuantityT<T2, U>) -> Self {
Self::new(num_traits::cast(other.amount()).unwrap())
}
fn cast_into(self) -> QuantityT<T2, U> {
QuantityT::<T2, U>::new(num_traits::cast(self.amount()).unwrap())
}
}
impl<R, E> std::fmt::Display for SIUnitsT<R, E>
where
R: SIRatios,
E: SIExponents,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut any = false;
// Length
let e = E::Length::to_i64();
if e != 0 {
assert!(R::Length::numerator() != 0 && R::Length::denominator() != 0);
any = true;
write_ratio_symbol_short::<R::Length>(f)?;
write!(f, "m")?;
if e != 1 {
write_superscript(e, f)?;
}
};
// Mass
let e = E::Mass::to_i64();
if e != 0 {
assert!(R::Mass::numerator() != 0 && R::Mass::denominator() != 0);
if any {
write!(f, "·")?;
}
any = true;
write_ratio_symbol_short::<R::Mass>(f)?;
write!(f, "g")?;
if e != 1 {
write_superscript(e, f)?;
}
};
// Time
let e = E::Time::to_i64();
if e != 0 {
assert!(R::Time::numerator() != 0 && R::Time::denominator() != 0);
if any {
write!(f, "·")?;
}
write_ratio_symbol_time_short::<R::Time>(f)?;
if e != 1 {
write_superscript(e, f)?;
}
};
Ok(())
}
}
fn write_superscript(num: i64, f: &mut fmt::Formatter) -> fmt::Result {
const NEG: &char = &'⁻';
const DIGITS: &[char; 10] = &[
'⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹',
];
let mut digits: [u8; 48] = [0; 48];
let mut num_digits = 0;
let mut num = num;
if num < 0 {
write!(f, "{}", NEG)?;
num = num.abs();
}
while num != 0 {
let digit = num % 10;
num /= 10;
digits[num_digits] = digit as u8;
num_digits += 1;
}
let slice = &digits[0..num_digits];
for digit in slice.iter().rev() {
let idx = *digit as usize;
write!(f, "{}", DIGITS[idx])?;
}
Ok(())
}
fn write_ratio_symbol_short<R: Ratio>(f: &mut fmt::Formatter) -> fmt::Result {
let n = R::numerator();
let d = R::denominator();
// Print Ratio; Kilo, centi, (18/7), etc
match (n, d) {
(1_000_000_000_000_000, 1) => write!(f, "P"),
(1_000_000_000_000, 1) => write!(f, "T"),
(1_000_000_000, 1) => write!(f, "G"),
(1_000_000, 1) => write!(f, "M"),
(1_000, 1) => write!(f, "k"),
(100, 1) => write!(f, "h"),
(10, 1) => write!(f, "da"),
(1, 1) => Ok(()),
(1, 10) => write!(f, "d"),
(1, 100) => write!(f, "c"),
(1, 1_000) => write!(f, "m"),
(1, 1_000_000) => write!(f, "μ"),
(1, 1_000_000_000) => write!(f, "n"),
(1, 1_000_000_000_000) => write!(f, "p"),
(1, 1_000_000_000_000_000) => write!(f, "f"),
(_, _) => write!(f, "({}/{})", n, d),
}
}
fn write_ratio_symbol_time_short<R: Ratio>(f: &mut fmt::Formatter) -> fmt::Result {
let n = R::numerator();
let d = R::denominator();
// Print Ratio; Kilo, centi, (18/7), etc
match (n, d) {
(3_600, 1) => write!(f, "h"),
(60, 1) => write!(f, "m"),
(1, 1) => write!(f, "s"),
(1, 10) => write!(f, "ds"),
(1, 100) => write!(f, "cs"),
(1, 1_000) => write!(f, "ms"),
(1, 1_000_000) => write!(f, "μs"),
(1, 1_000_000_000) => write!(f, "ns"),
(1, 1_000_000_000_000) => write!(f, "ps"),
(1, 1_000_000_000_000_000) => write!(f, "fs"),
(_, _) => write!(f, "({}/{})", n, d),
}
}
impl<R, E> Sqrt for SIUnitsT<R, E>
where
R: SIRatios,
E: SIExponents,
E::Length: PartialDiv<P2>,
E::Mass: PartialDiv<P2>,
E::Time: PartialDiv<P2>,
<E::Length as PartialDiv<P2>>::Output: Integer,
<E::Mass as PartialDiv<P2>>::Output: Integer,
<E::Time as PartialDiv<P2>>::Output: Integer,
SIUnitsT<
R,
SIExponentsT<
<E::Length as PartialDiv<P2>>::Output,
<E::Mass as PartialDiv<P2>>::Output,
<E::Time as PartialDiv<P2>>::Output,
>,
>: Default,
{
type Output = SIUnitsT<
R,
SIExponentsT<
<E::Length as PartialDiv<P2>>::Output,
<E::Mass as PartialDiv<P2>>::Output,
<E::Time as PartialDiv<P2>>::Output,
>,
>;
fn sqrt(self) -> Self::Output {
Default::default()
}
}
impl<T, R, U> std::ops::Mul<RatioExponentT<R, typenum::PInt<U>>> for AmountT<T>
where
T: Amount + std::ops::Mul<T> + num_traits::NumCast,
R: Ratio,
U: typenum::Unsigned + NonZero,
MulOutput<T, T>: std::ops::Div<T, Output = T>,
{
type Output = AmountT<T>;
fn mul(self, _other: RatioExponentT<R, typenum::PInt<U>>) -> Self::Output {
let amount = self.0;
let n: T = num_traits::cast(R::numerator()).unwrap();
let d: T = num_traits::cast(R::denominator()).unwrap();
let result = amount * n / d;
AmountT(result)
}
}
impl<T, R, U> std::ops::Div<RatioExponentT<R, typenum::PInt<U>>> for AmountT<T>
where
T: Amount + std::ops::Mul<T> + num_traits::NumCast,
R: Ratio,
U: typenum::Unsigned + NonZero,
MulOutput<T, T>: std::ops::Div<T, Output = T>,
{
type Output = AmountT<T>;
fn div(self, _other: RatioExponentT<R, typenum::PInt<U>>) -> Self::Output {
let amount = self.0;
let n: T = num_traits::cast(R::numerator()).unwrap();
let d: T = num_traits::cast(R::denominator()).unwrap();
let result = amount * d / n;
AmountT(result)
}
}
impl<T, R, U> std::ops::Mul<RatioExponentT<R, typenum::NInt<U>>> for AmountT<T>
where
T: Amount + std::ops::Mul<T> + num_traits::NumCast,
R: Ratio,
U: typenum::Unsigned + NonZero,
MulOutput<T, T>: std::ops::Div<T, Output = T>,
{
type Output = AmountT<T>;
fn mul(self, _other: RatioExponentT<R, typenum::NInt<U>>) -> Self::Output {
let amount = self.0;
let n: T = num_traits::cast(R::numerator()).unwrap();
let d: T = num_traits::cast(R::denominator()).unwrap();
let result = amount * d / n;
AmountT(result)
}
}
impl<T, R, U> std::ops::Div<RatioExponentT<R, typenum::NInt<U>>> for AmountT<T>
where
T: Amount + std::ops::Mul<T> + num_traits::NumCast,
R: Ratio,
U: typenum::Unsigned + NonZero,
MulOutput<T, T>: std::ops::Div<T, Output = T>,
{
type Output = AmountT<T>;
fn div(self, _other: RatioExponentT<R, typenum::NInt<U>>) -> Self::Output {
let amount = self.0;
let n: T = num_traits::cast(R::numerator()).unwrap();
let d: T = num_traits::cast(R::denominator()).unwrap();
let result = amount * n / d;
AmountT(result)
}
}
impl<T> std::ops::Mul<RatioExponentT<RatioZero, typenum::Z0>> for AmountT<T>
where
T: Amount,
{
type Output = AmountT<T>;
fn mul(self, _other: RatioExponentT<RatioZero, typenum::Z0>) -> AmountT<T> {
self
}
}
impl<T> std::ops::Div<RatioExponentT<RatioZero, typenum::Z0>> for AmountT<T>
where
T: Amount,
{
type Output = AmountT<T>;
fn div(self, _other: RatioExponentT<RatioZero, typenum::Z0>) -> AmountT<T> {
self
}
}
/// Convenient `RatioT` types — Mega, Kilo, Centi, Nano, etc.
pub mod ratios {
use super::*;
use typenum::Exp;
// Limited to 10^15 with i64.
// Can cover "full" 10^24 spectrum with i128 support
// Missing: Yotta
// Missing: Zetta
// Missing: Exa
pub type Peta = RatioT<Exp<P10, P15>, P1>;
pub type Tera = RatioT<Exp<P10, P12>, P1>;
pub type Giga = RatioT<Exp<P10, P9>, P1>;
pub type Mega = RatioT<Exp<P10, P6>, P1>;
pub type Kilo = RatioT<Exp<P10, P3>, P1>;
pub type Hecto = RatioT<Exp<P10, P2>, P1>;
pub type Deca = RatioT<Exp<P10, P1>, P1>;
pub type Zero = RatioZero;
pub type Unit = RatioT<P1, P1>;
pub type Deci = RatioT<P1, Exp<P10, P1>>;
pub type Centi = RatioT<P1, Exp<P10, P2>>;
pub type Milli = RatioT<P1, Exp<P10, P3>>;
pub type Micro = RatioT<P1, Exp<P10, P6>>;
pub type Nano = RatioT<P1, Exp<P10, P9>>;
pub type Pico = RatioT<P1, Exp<P10, P12>>;
pub type Femto = RatioT<P1, Exp<P10, P15>>;
// Missing: Atto
// Missing: Zepto
// Missing: Yocto
// Time
pub type Second = Unit;
pub type Minute = RatioT<P60, P1>;
pub type Hour = RatioT<P3600, P1>;
pub type Day = RatioT<P86400, P1>;
pub type Year = RatioT<P31536000, P1>;
// BaseTypes
pub type Length<R> = SIRatiosT<R, Zero, Zero>;
pub type Mass<R> = SIRatiosT<Zero, R, Zero>;
pub type Time<R> = SIRatiosT<Zero, Zero, R>;
}
/// Convenient `SIExponentsT` types — Length, Mass, etc.
pub mod exponents {
use super::*;
// Base types
pub type Length = SIExponentsT<P1, Z0, Z0>;
pub type Mass = SIExponentsT<Z0, P1, Z0>;
pub type Time = SIExponentsT<Z0, Z0, P1>;
}
/// Convenient `SIUnitsT` types — `Length<R>`, `Mass<R>`, etc.
pub mod units {
use super::*;
pub type Dimensionless = SIUnitsT<SIRatiosT<Zero, Zero, Zero>, SIExponentsT<Z0, Z0, Z0>>;
pub type Length<R> = SIUnitsT<ratios::Length<R>, exponents::Length>;
pub type Mass<R> = SIUnitsT<ratios::Mass<R>, exponents::Mass>;
pub type Time<R> = SIUnitsT<ratios::Time<R>, exponents::Time>;
}
/// Convenient `QuantityT` types — `Length<T,R>`, `Velocity<T,RL,RT>`, etc.
pub mod quantities {
use super::*;
// Base
pub type Dimensionless<T> =
QuantityT<T, SIUnitsT<SIRatiosT<Zero, Zero, Zero>, SIExponentsT<Z0, Z0, Z0>>>;
pub type Length<T, R> = QuantityT<T, units::Length<R>>;
pub type Mass<T, R> = QuantityT<T, units::Mass<R>>;
pub type Time<T, R> = QuantityT<T, units::Time<R>>;
// Derived
pub type Area<T, R> =
QuantityT<T, SIUnitsT<SIRatiosT<R, Zero, Zero>, SIExponentsT<P2, Z0, Z0>>>;
pub type Velocity<T, RL, RT> =
QuantityT<T, SIUnitsT<SIRatiosT<RL, Zero, RT>, SIExponentsT<P1, Z0, N1>>>;
pub type Acceleration<T, RL, RT> =
QuantityT<T, SIUnitsT<SIRatiosT<RL, Zero, RT>, SIExponentsT<P1, Z0, N2>>>;
// Lengths
pub type Petameters<T> = Length<T, Peta>;
pub type Terameters<T> = Length<T, Tera>;
pub type Gigameters<T> = Length<T, Giga>;
pub type Megameters<T> = Length<T, Mega>;
pub type Kilometers<T> = Length<T, Kilo>;
pub type Hectometers<T> = Length<T, Hecto>;
pub type Decameters<T> = Length<T, Deca>;
pub type Zerometers<T> = Length<T, Zero>;
pub type Meters<T> = Length<T, Unit>;
pub type Decimeters<T> = Length<T, Deci>;
pub type Centimeters<T> = Length<T, Centi>;
pub type Millimeters<T> = Length<T, Milli>;
pub type Micrometers<T> = Length<T, Micro>;
pub type Nanometers<T> = Length<T, Nano>;
pub type Picometers<T> = Length<T, Pico>;
pub type Femtometers<T> = Length<T, Femto>;
// Masses
pub type Petagrams<T> = Mass<T, Peta>;
pub type Teragrams<T> = Mass<T, Tera>;
pub type Gigagrams<T> = Mass<T, Giga>;
pub type Megagrams<T> = Mass<T, Mega>;
pub type Kilograms<T> = Mass<T, Kilo>;
pub type Hectograms<T> = Mass<T, Hecto>;
pub type Decagrams<T> = Mass<T, Deca>;
pub type Zerograms<T> = Mass<T, Zero>;
pub type Grams<T> = Mass<T, Unit>;
pub type Decigrams<T> = Mass<T, Deci>;
pub type Centigrams<T> = Mass<T, Centi>;
pub type Milligrams<T> = Mass<T, Milli>;
pub type Micrograms<T> = Mass<T, Micro>;
pub type Nanograms<T> = Mass<T, Nano>;
pub type Picograms<T> = Mass<T, Pico>;
pub type Femtograms<T> = Mass<T, Femto>;
// Times
pub type Years<T> = Time<T, Year>;
pub type Days<T> = Time<T, Day>;
pub type Hours<T> = Time<T, Hour>;
pub type Minutes<T> = Time<T, Minute>;
pub type Seconds<T> = Time<T, Unit>;
pub type Milliseconds<T> = Time<T, Milli>;
pub type Microseconds<T> = Time<T, Micro>;
pub type Nanoseconds<T> = Time<T, Nano>;
pub type Picoseconds<T> = Time<T, Pico>;
pub type Femtoseconds<T> = Time<T, Femto>;
// Derived Units
pub type MetersPerSecond<T> = Velocity<T, Unit, Unit>;
pub type MetersPerSecond2<T> = Acceleration<T, Unit, Unit>;
pub type KilometersPerHour<T> = Velocity<T, Kilo, Hour>;
/// Convenient `QuantityT<f32, SIUnitsT<_,_>>` types — Meters, Kilograms, MetersPerSecond, etc.
pub mod f32 {
use super::*;
pub type Dimensionless =
QuantityT<f32, SIUnitsT<SIRatiosT<Zero, Zero, Zero>, SIExponentsT<Z0, Z0, Z0>>>;
pub type Length<R> = QuantityT<f32, units::Length<R>>;
pub type Mass<R> = QuantityT<f32, units::Mass<R>>;
pub type Time<R> = QuantityT<f32, units::Time<R>>;
pub type Petameters = Length<Peta>;
pub type Terameters = Length<Tera>;
pub type Gigameters = Length<Giga>;
pub type Megameters = Length<Mega>;
pub type Kilometers = Length<Kilo>;
pub type Hectometers = Length<Hecto>;
pub type Decameters = Length<Deca>;
pub type Zerometers = Length<Zero>;
pub type Meters = Length<Unit>;
pub type Decimeters = Length<Deci>;
pub type Centimeters = Length<Centi>;
pub type Millimeters = Length<Milli>;
pub type Micrometers = Length<Micro>;
pub type Nanometers = Length<Nano>;
pub type Picometers = Length<Pico>;
pub type Femtometers = Length<Femto>;
pub type Kilograms = Mass<Kilo>;
pub type Grams = Mass<Unit>;
pub type Years = Time<Year>;
pub type Days = Time<Day>;
pub type Hours = Time<Hour>;
pub type Minutes = Time<Minute>;
pub type Seconds = Time<Unit>;
pub type Milliseconds = Time<Milli>;
pub type Microseconds = Time<Micro>;
pub type Nanoseconds = Time<Nano>;
pub type Picoseconds = Time<Pico>;
pub type Femtoseconds = Time<Femto>;
pub type MetersPerSecond = super::MetersPerSecond<f32>;
pub type MetersPerSecond2 = super::MetersPerSecond2<f32>;
pub type KilometersPerHour = super::KilometersPerHour<f32>;
}
/// Dimensional traits for `impl Trait` use — `trait Length`, `trait Velocity`, etc.
pub mod traits {
use crate::quantity::{Amount, ConvertUnits, Quantity};
use crate::ratio::Ratio;
// --------------------------------
// Trait Declarations
// --------------------------------
pub trait Length: Quantity + Copy {
fn convert<R>(self) -> super::Length<Self::AmountType, R>
where
R: Ratio,
Self::AmountType: Amount;
}
pub trait Time: Quantity + Copy {
fn convert<R>(self) -> super::Time<Self::AmountType, R>
where
R: Ratio,
Self::AmountType: Amount;
}
pub trait Area: Quantity {
fn convert<R>(self) -> super::Area<Self::AmountType, R>
where
R: Ratio,
Self::AmountType: Amount;
}
pub trait Velocity: Quantity {
fn convert<RL, RT>(self) -> super::Velocity<Self::AmountType, RL, RT>
where
RL: Ratio,
RT: Ratio,
Self::AmountType: Amount;
}
pub trait Acceleration: Quantity {
fn convert<RL, RT>(self) -> super::Acceleration<Self::AmountType, RL, RT>
where
RL: Ratio,
RT: Ratio,
Self::AmountType: Amount;
}
// --------------------------------
// Trait Impls
// --------------------------------
impl<T, R> Length for super::Length<T, R>
where
T: Amount
+ num_traits::NumCast
+ std::ops::Mul<T, Output = T>
+ std::ops::Div<T, Output = T>,
R: Ratio + Copy,
{
fn convert<R2>(self) -> super::Length<Self::AmountType, R2>
where
R2: Ratio,
Self: super::ConvertUnits<super::Length<Self::AmountType, R2>>,
{
self.convert_into()
}
}
impl<T, R> Time for super::Time<T, R>
where
T: Amount
+ num_traits::NumCast
+ std::ops::Mul<T, Output = T>
+ std::ops::Div<T, Output = T>,
R: Ratio + Copy,
{
fn convert<R2>(self) -> super::Time<Self::AmountType, R2>
where
R2: Ratio,
Self: super::ConvertUnits<super::Time<Self::AmountType, R2>>,
{
self.convert_into()
}
}
impl<T, R> Area for super::Area<T, R>
where
T: Amount
+ num_traits::NumCast
+ std::ops::Mul<T, Output = T>
+ std::ops::Div<T, Output = T>,
R: Ratio + Copy,
{
fn convert<R2>(self) -> super::Area<Self::AmountType, R2>
where
R2: Ratio,
Self: super::ConvertUnits<super::Area<Self::AmountType, R2>>,
{
self.convert_into()
}
}
impl<T, RL, RT> Velocity for super::Velocity<T, RL, RT>
where
T: Amount
+ num_traits::NumCast
+ std::ops::Mul<T, Output = T>
+ std::ops::Div<T, Output = T>,
RL: Ratio + Copy,
RT: Ratio + Copy,
{
fn convert<RL2, RT2>(self) -> super::Velocity<Self::AmountType, RL2, RT2>
where
RL2: Ratio,
RT2: Ratio,
Self: super::ConvertUnits<super::Velocity<Self::AmountType, RL2, RT2>>,
{
self.convert_into()
}
}
impl<T, RL, RT> Acceleration for super::Acceleration<T, RL, RT>
where
T: Amount
+ num_traits::NumCast
+ std::ops::Mul<T, Output = T>
+ std::ops::Div<T, Output = T>,
RL: Ratio + Copy,
RT: Ratio + Copy,
{
fn convert<RL2, RT2>(self) -> super::Acceleration<Self::AmountType, RL2, RT2>
where
RL2: Ratio,
RT2: Ratio,
Self: super::ConvertUnits<super::Acceleration<Self::AmountType, RL2, RT2>>,
{
self.convert_into()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment