use crate::dimension::{DimDiv, DimMul, Dimension};
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)
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::units::length::{Kilometer, Meter};
use crate::units::time::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}");
}
}