use core::fmt::Debug;
use crate::Real;
use crate::model::{measure::Measure, quantity::QuantityMarker};
pub trait Unit: Debug + Clone + Copy + PartialEq + PartialOrd {
type Quantity: QuantityMarker;
const FACTOR: Real;
const OFFSET: Real;
const ABBREV: &'static str;
fn new(value: impl Into<Real>) -> Measure<Self> {
Measure::new(value.into())
}
}
#[macro_export]
macro_rules! unit {
(base: $unit_name:ident, $abbrev:literal, $quantity:ty $(; $($optionals:tt)*)? ) => {
unit!(base_internal: $unit_name, $abbrev, $quantity, 1.0; $($($optionals)*)?);
};
(base_internal: $unit_name:ident, $abbrev:literal, $quantity:ty, $factor:expr; prefixable $(, $($optionals:tt)*)?) => {
unit!(base_internal: $unit_name, $abbrev, $quantity, $factor; $($($optionals)*)?);
impl $crate::__model::Prefixable for $unit_name {}
};
(base_internal: $unit_name:ident, $abbrev:literal, $quantity:ty, $factor:expr; factor = $new_factor:expr $(, $($optionals:tt)*)?) => {
unit!(base_internal: $unit_name, $abbrev, $quantity, $new_factor; $($($optionals)*)?);
};
(base_internal: $unit_name:ident, $abbrev:literal, $quantity:ty, $factor:expr;) => {
$crate::__unit!($unit_name, $quantity, $factor, $abbrev);
};
(derived: $unit_name:ident, $abbrev:literal, ($factor:expr, $base_unit:ty); prefixable) => {
unit!(derived: $unit_name, $abbrev, ($factor, $base_unit));
impl $crate::__model::Prefixable for $unit_name {}
};
(derived: $unit_name:ident, $abbrev:literal, ($factor:expr, $base_unit:ty)) => {
unit!(derived: $unit_name, $abbrev, ($factor, 0.0, $base_unit));
};
(derived: $unit_name:ident, $abbrev:literal, ($factor:expr, $offset:expr, $base_unit:ty)) => {
$crate::__unit!(
$unit_name,
<$base_unit as $crate::__model::Unit>::Quantity,
($factor as $crate::Real) * <$base_unit as $crate::__model::Unit>::FACTOR,
<$base_unit as $crate::__model::Unit>::OFFSET + (<$base_unit as $crate::__model::Unit>::FACTOR * $offset),
$abbrev
);
};
(compound: $unit_name:ident, $abbrev:literal, [$($components:tt),+] $(; $($optionals:tt)*)? ) => {
unit!(compound_internal: $unit_name, $abbrev, (), [$($components),+]; $($($optionals)*)?);
};
(compound_internal: $unit_name:ident, $abbrev:literal, $name_tag:ty, [$($components:tt),+]; prefixable $(, $($optionals:tt)*)?) => {
unit!(compound_internal: $unit_name, $abbrev, $name_tag, [$($components),+]; $($($optionals)*)?);
impl $crate::__model::Prefixable for $unit_name {}
};
(compound_internal: $unit_name:ident, $abbrev:literal, $name_tag:ty, [$($components:tt),+]; marked $quantity_for_tag:ty $(, $($optionals:tt)*)?) => {
unit!(compound_internal:
$unit_name,
$abbrev,
<$quantity_for_tag as $crate::__model::QuantityMarker>::Tag,
[$($components),+];
$($($optionals)*)?
);
};
(compound_internal: $unit_name:ident, $abbrev:literal, $quantity_for_tag:ty, [$($components:tt),+];) => {
$crate::__compound_unit!(
$unit_name,
$abbrev,
$quantity_for_tag,
[
$crate::__model::Quantity<$crate::__model::DimensionZero, ()>,
1.0 as $crate::Real;
$($components),+
]
);
};
(prefix: $alias:ident, $prefix:ty, $base_unit:ty) => {
$crate::__unit!(
$alias,
<$base_unit as $crate::__model::Unit>::Quantity,
<$prefix as $crate::__model::Prefix>::FACTOR * <$base_unit as $crate::__model::Unit>::FACTOR,
const_format::concatcp!(
<$prefix as $crate::__model::Prefix>::SYMBOL,
<$base_unit as $crate::__model::Unit>::ABBREV
)
);
};
}
#[doc(hidden)]
pub mod __inner_unit_macros {
use crate::Real;
#[macro_export]
#[doc(hidden)]
macro_rules! __unit {
($unit_name:ident, $quantity:ty, $factor:expr, $abbrev:expr) => {
$crate::__unit!($unit_name, $quantity, $factor, 0.0, $abbrev);
};
($unit_name:ident, $quantity:ty, $factor:expr, $offset:expr, $abbrev:expr) => {
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct $unit_name;
impl $crate::__model::Unit for $unit_name {
type Quantity = $quantity;
const FACTOR: $crate::Real = $factor;
const OFFSET: $crate::Real = $offset;
const ABBREV: &'static str = $abbrev;
}
impl core::fmt::Display for $unit_name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", <$unit_name as $crate::__model::Unit>::ABBREV)
}
}
impl<U: $crate::__model::Unit> core::ops::Mul<U> for $unit_name
where
$quantity: core::ops::Mul<U::Quantity>,
{
type Output =
<$crate::Measure<$unit_name> as core::ops::Mul<$crate::Measure<U>>>::Output;
fn mul(self, _rhs: U) -> Self::Output {
$crate::Measure::<$unit_name>::new(1.0) * $crate::Measure::<U>::new(1.0)
}
}
#[cfg(feature = "f64")]
$crate::__unit_times_number!($unit_name, [i32, f64]);
#[cfg(feature = "f32")]
$crate::__unit_times_number!($unit_name, [i16, f32]);
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __compound_unit {
($unit_name:ident, $abbrev:literal, $name_tag:ty, [$quantity:ty, $factor_acc:expr;] ) => {
$crate::__unit!(
$unit_name,
$crate::__model::Quantity<
<$quantity as $crate::__model::QuantityMarker>::DimensionVector,
$name_tag
>,
$factor_acc,
$abbrev
);
};
($unit_name:ident, $abbrev:literal, $name_tag:ty, [$quantity:ty, $factor_acc:expr; ($unit:ty, $exp:ty) $(, $components:tt)*] ) => {
$crate::__compound_unit!(
$unit_name,
$abbrev,
$name_tag,
[$quantity, $factor_acc; (1.0 as $crate::Real, $unit, $exp) $(, $components)*]
);
};
($unit_name:ident, $abbrev:literal, $name_tag:ty, [$quantity:ty, $factor_acc:expr; ($scalar:expr, $unit:ty, $exp:ty) $(, $components:tt)*] ) => {
$crate::__compound_unit!(
$unit_name,
$abbrev,
$name_tag,
[
<$quantity as core::ops::Mul<
<<$unit as $crate::__model::Unit>::Quantity as $crate::__model::TypePow<$exp>>::Output
>>::Output,
$factor_acc * $crate::__model::__inner_unit_macros::__powi_const(
($scalar as $crate::Real) * <$unit as $crate::__model::Unit>::FACTOR,
<$exp as typenum::ToInt<i32>>::INT
);
$($components),*
]
);
};
($unit_name:ident, $abbrev:literal, $quantity_for_tag:ty, [$quantity:ty, $factor_acc:expr; (constant $constant:expr, $unit:ty, $exp:ty) $(, $components:tt)*] ) => {
$crate::__compound_unit!(
$unit_name,
$abbrev,
$quantity_for_tag,
[
<$quantity as core::ops::Mul<
<<$unit as $crate::__model::Unit>::Quantity as $crate::__model::TypePow<$exp>>::Output
>>::Output,
$factor_acc * $crate::__model::__inner_unit_macros::__powi_const(
($constant).value_const(), <$exp as typenum::ToInt<i32>>::INT
);
$($components),*
]
);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __unit_times_number {
($unit:ty, [$($numeric_type:ty),*]) => {
$(
impl core::ops::Mul<$unit> for $numeric_type {
type Output = $crate::Measure<$unit>;
fn mul(self, _rhs: $unit) -> Self::Output {
<$unit as $crate::__model::Unit>::new(self)
}
}
)*
};
}
#[doc(hidden)]
pub const fn __powi_const(mut base: Real, mut exp: i32) -> Real {
if exp == 0 {
return 1.0;
}
let neg = exp < 0;
if neg {
exp = -exp;
}
let mut e = exp as u32;
let mut acc = 1.0;
while e != 0 {
if (e & 1) == 1 {
acc *= base;
}
base *= base;
e >>= 1;
}
if neg { 1.0 / acc } else { acc }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::system::*;
#[test]
fn test_unit_new_method() {
let distance_f32 = Metre::new(100.0f32);
let distance_i16 = Metre::new(100i16);
let distance_u16 = Metre::new(100_u16);
assert_eq!(distance_f32.value(), 100.0);
assert_eq!(distance_i16.value(), 100.0);
assert_eq!(distance_u16.value(), 100.0);
let mass = Kilogram::new(5.5);
let time = Second::new(10i16);
let force = Newton::new(50.0);
assert_eq!(mass.value(), 5.5);
assert_eq!(time.value(), 10.0);
assert_eq!(force.value(), 50.0);
let _: Measure<Metre> = Metre::new(1.0);
let _: Measure<Kilogram> = Kilogram::new(1.0);
let _: Measure<Second> = Second::new(1.0);
}
#[test]
fn test_unit_trait_implementation() {
fn test_unit_trait<U: Unit>(_unit: U) -> (Real, Real, &'static str) {
(U::FACTOR, U::OFFSET, U::ABBREV)
}
let (factor, offset, abbrev) = test_unit_trait(Metre);
assert_eq!(factor, 1.0);
assert_eq!(offset, 0.0);
assert_eq!(abbrev, "m");
let (factor, offset, abbrev) = test_unit_trait(Kilometre);
assert_eq!(factor, 1000.0);
assert_eq!(offset, 0.0);
assert_eq!(abbrev, "km");
let (factor, offset, abbrev) = test_unit_trait(DegreeCelsius);
assert_eq!(factor, 1.0);
assert_eq!(offset, 273.15);
assert_eq!(abbrev, "°C");
}
#[test]
fn test_custom_powi_function() {
use super::__inner_unit_macros::__powi_const;
let square: Real = __powi_const(2.0, 2);
let cube: Real = __powi_const(3.0, 3);
let fourth_power: Real = __powi_const(10.0, 4);
assert_eq!(square, 4.0); assert_eq!(cube, 27.0); assert_eq!(fourth_power, 10000.0);
let zero_exp: Real = __powi_const(42.0, 0);
assert_eq!(zero_exp, 1.0);
let neg_square: Real = __powi_const(2.0, -2);
let neg_cube: Real = __powi_const(4.0, -3);
assert_eq!(neg_square, 0.25); assert_eq!(neg_cube, 1.0 / 64.0);
let one_to_power: Real = __powi_const(1.0, 100);
let first_power: Real = __powi_const(5.0, 1);
assert_eq!(one_to_power, 1.0); assert_eq!(first_power, 5.0);
let acceleration_factor: Real = 1.0 * __powi_const(1.0, -2); assert_eq!(acceleration_factor, 1.0);
}
}