use crate::dimension::{DimDiv, DimMul, Dimension, Dimensionless};
use crate::scalar::Scalar;
use crate::Quantity;
use core::fmt::{Debug, Display, Formatter, LowerExp, Result, UpperExp};
use core::marker::PhantomData;
pub trait Unit: Copy + PartialEq + Debug + 'static {
const RATIO: f64;
type Dim: Dimension;
const SYMBOL: &'static str;
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Per<N: Unit, D: Unit>(PhantomData<(N, D)>);
impl<N: Unit, D: Unit> Unit for Per<N, D>
where
N::Dim: DimDiv<D::Dim>,
<N::Dim as DimDiv<D::Dim>>::Output: Dimension,
{
const RATIO: f64 = N::RATIO / D::RATIO;
type Dim = <N::Dim as DimDiv<D::Dim>>::Output;
const SYMBOL: &'static str = "";
}
impl<N: Unit, D: Unit, S: Scalar + Display> Display for Quantity<Per<N, D>, S>
where
N::Dim: DimDiv<D::Dim>,
<N::Dim as DimDiv<D::Dim>>::Output: Dimension,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Display::fmt(&self.value(), f)?;
write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
}
}
impl<N: Unit, D: Unit, S: Scalar + LowerExp> LowerExp for Quantity<Per<N, D>, S>
where
N::Dim: DimDiv<D::Dim>,
<N::Dim as DimDiv<D::Dim>>::Output: Dimension,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
LowerExp::fmt(&self.value(), f)?;
write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
}
}
impl<N: Unit, D: Unit, S: Scalar + UpperExp> UpperExp for Quantity<Per<N, D>, S>
where
N::Dim: DimDiv<D::Dim>,
<N::Dim as DimDiv<D::Dim>>::Output: Dimension,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
UpperExp::fmt(&self.value(), f)?;
write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Prod<A: Unit, B: Unit>(PhantomData<(A, B)>);
impl<A: Unit, B: Unit> Unit for Prod<A, B>
where
A::Dim: DimMul<B::Dim>,
<A::Dim as DimMul<B::Dim>>::Output: Dimension,
{
const RATIO: f64 = A::RATIO * B::RATIO;
type Dim = <A::Dim as DimMul<B::Dim>>::Output;
const SYMBOL: &'static str = "";
}
impl<A: Unit, B: Unit, S: Scalar + Display> Display for Quantity<Prod<A, B>, S>
where
A::Dim: DimMul<B::Dim>,
<A::Dim as DimMul<B::Dim>>::Output: Dimension,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Display::fmt(&self.value(), f)?;
write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
}
}
impl<A: Unit, B: Unit, S: Scalar + LowerExp> LowerExp for Quantity<Prod<A, B>, S>
where
A::Dim: DimMul<B::Dim>,
<A::Dim as DimMul<B::Dim>>::Output: Dimension,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
LowerExp::fmt(&self.value(), f)?;
write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
}
}
impl<A: Unit, B: Unit, S: Scalar + UpperExp> UpperExp for Quantity<Prod<A, B>, S>
where
A::Dim: DimMul<B::Dim>,
<A::Dim as DimMul<B::Dim>>::Output: Dimension,
{
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
UpperExp::fmt(&self.value(), f)?;
write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Unitless;
impl Unit for Unitless {
const RATIO: f64 = 1.0;
type Dim = Dimensionless;
const SYMBOL: &'static str = "";
}
impl<S: Scalar + Display> Display for Quantity<Unitless, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Display::fmt(&self.value(), f)
}
}
impl<S: Scalar + LowerExp> LowerExp for Quantity<Unitless, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
LowerExp::fmt(&self.value(), f)
}
}
impl<S: Scalar + UpperExp> UpperExp for Quantity<Unitless, S> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
UpperExp::fmt(&self.value(), f)
}
}
pub trait Simplify {
type Scalar: Scalar;
type Out: Unit;
fn simplify(self) -> Quantity<Self::Out, Self::Scalar>;
}
impl<U: Unit, S: Scalar> Simplify for Quantity<Per<U, U>, S>
where
U::Dim: DimDiv<U::Dim>,
<U::Dim as DimDiv<U::Dim>>::Output: Dimension,
{
type Scalar = S;
type Out = Unitless;
fn simplify(self) -> Quantity<Unitless, S> {
Quantity::new(self.value())
}
}
impl<N: Unit, D: Unit, S: Scalar> Simplify for Quantity<Per<N, Per<N, D>>, S>
where
N::Dim: DimDiv<D::Dim>,
<N::Dim as DimDiv<D::Dim>>::Output: Dimension,
N::Dim: DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>,
<N::Dim as DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>>::Output: Dimension,
{
type Scalar = S;
type Out = D;
fn simplify(self) -> Quantity<D, S> {
Quantity::new(self.value())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::units::length::{Kilometer, Meter, Meters};
use crate::units::time::{Hour, Second};
use crate::Quantity;
#[test]
fn per_display_formats_value_and_symbol() {
let qty: Quantity<Per<Meter, Second>> = Quantity::new(10.0);
let s = format!("{qty}");
assert_eq!(s, "10 m/s");
}
#[test]
fn per_display_with_precision() {
let qty: Quantity<Per<Meter, Second>> = Quantity::new(1.5);
let s = format!("{qty:.2}");
assert_eq!(s, "1.50 m/s");
}
#[test]
fn per_lower_exp_formats_correctly() {
let qty: Quantity<Per<Meter, Second>> = Quantity::new(1000.0);
let s = format!("{qty:e}");
assert!(s.contains("e"), "Expected scientific notation, got: {s}");
assert!(s.ends_with("m/s"), "Expected 'm/s' suffix, got: {s}");
}
#[test]
fn per_upper_exp_formats_correctly() {
let qty: Quantity<Per<Meter, Second>> = Quantity::new(1000.0);
let s = format!("{qty:E}");
assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
assert!(s.ends_with("m/s"), "Expected 'm/s' suffix, got: {s}");
}
#[test]
fn prod_display_formats_value_and_symbol() {
let qty: Quantity<Prod<Meter, Second>> = Quantity::new(3.0);
let s = format!("{qty}");
assert_eq!(s, "3 m·s");
}
#[test]
fn prod_display_with_precision() {
let qty: Quantity<Prod<Meter, Second>> = Quantity::new(2.5);
let s = format!("{qty:.3}");
assert_eq!(s, "2.500 m·s");
}
#[test]
fn prod_lower_exp_formats_correctly() {
let qty: Quantity<Prod<Kilometer, Second>> = Quantity::new(5000.0);
let s = format!("{qty:.2e}");
assert!(s.contains("e"), "Expected scientific notation, got: {s}");
assert!(s.ends_with("km·s"), "Expected 'km·s' suffix, got: {s}");
}
#[test]
fn prod_upper_exp_formats_correctly() {
let qty: Quantity<Prod<Kilometer, Second>> = Quantity::new(5000.0);
let s = format!("{qty:.2E}");
assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
assert!(s.ends_with("km·s"), "Expected 'km·s' suffix, got: {s}");
}
#[test]
fn unitless_lower_exp_formats_correctly() {
let qty: Quantity<Unitless> = Quantity::new(0.5);
let s = format!("{qty:e}");
assert!(s.contains("e"), "Expected scientific notation, got: {s}");
assert!(
!s.contains(' '),
"Unitless should not have a space, got: {s}"
);
}
#[test]
fn unitless_upper_exp_formats_correctly() {
let qty: Quantity<Unitless> = Quantity::new(0.5);
let s = format!("{qty:E}");
assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
}
#[test]
fn simplify_per_u_u_gives_unitless() {
let ratio = Meters::new(3.0) / Meters::new(6.0);
let unitless: Quantity<Unitless> = ratio.simplify();
assert!((unitless.value() - 0.5).abs() < 1e-12);
}
#[test]
fn simplify_per_n_per_n_d_gives_d() {
let q: Quantity<Per<Meter, Per<Meter, Hour>>> = Quantity::new(42.0);
let simplified: Quantity<Hour> = q.simplify();
assert!((simplified.value() - 42.0).abs() < 1e-12);
}
}