use core::{
fmt::Display,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
};
use num::{traits::Pow, NumCast};
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InconsistentUnits {
expected: SI,
found: SI,
}
impl Display for InconsistentUnits {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Expected ")?;
self.expected.fmt(f)?;
f.write_str("; Found ")?;
self.found.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FormatAsError<'a> {
IncompatibleUnits(InconsistentUnits),
ParseError(crate::ParseError<'a>),
}
impl<'a> Display for FormatAsError<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::IncompatibleUnits(v) => v.fmt(f),
Self::ParseError(v) => v.fmt(f),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for InconsistentUnits {}
#[cfg(feature = "std")]
impl<'a> std::error::Error for FormatAsError<'a> {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DynQuantity<V>(pub V, pub SI);
impl<V: Display> Display for DynQuantity<V> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.0.fmt(f)?;
f.write_str(" ")?;
self.1.fmt(f)?;
Ok(())
}
}
impl<V> DynQuantity<V> {
pub fn powi(self, exp: i32) -> DynQuantity<V::Output>
where
V: Pow<i32>,
{
DynQuantity(self.0.pow(exp), self.1.powi(exp))
}
pub fn powf(self, exp: (i32, u32)) -> DynQuantity<V::Output>
where
V: Pow<f64>,
{
DynQuantity(self.0.pow(exp.0 as f64 / exp.1 as f64), self.1.powf(exp))
}
pub fn convert_to(mut self, mut new_units: SI) -> Result<DynQuantity<V>, InconsistentUnits>
where
V: Mul<V, Output = V> + Div<V, Output = V> + NumCast,
{
if !self.1.same_dimension(new_units) {
self.1.scale = (1, 1);
new_units.scale = (1, 1);
return Err(InconsistentUnits {
expected: self.1,
found: new_units,
});
}
let scale = self.1.div(new_units).scale;
Ok(DynQuantity(
self.0 * V::from(scale.0).expect("Casting the scale value to type V to work")
/ V::from(scale.1).expect("Casting the scale value to type V to work"),
new_units,
))
}
pub fn checked_add<R>(
self,
rhs: DynQuantity<R>,
) -> Result<DynQuantity<<V as Add<R>>::Output>, InconsistentUnits>
where
V: Add<R>,
{
if self.1 != rhs.1 {
return Err(InconsistentUnits {
expected: self.1,
found: rhs.1,
});
}
Ok(DynQuantity(self.0 + rhs.0, self.1))
}
pub fn checked_sub<R>(
self,
rhs: DynQuantity<R>,
) -> Result<DynQuantity<<V as Sub<R>>::Output>, InconsistentUnits>
where
V: Sub<R>,
{
if self.1 != rhs.1 {
return Err(InconsistentUnits {
expected: self.1,
found: rhs.1,
});
}
Ok(DynQuantity(self.0 - rhs.0, self.1))
}
}
impl<V: Display> DynQuantity<V> {
#[allow(private_bounds)]
pub fn write_as(
&self,
units: &'static str,
f: &mut core::fmt::Formatter,
) -> Result<(), core::fmt::Error> {
if Ok(self.1) != si_checked(units) {
return Err(core::fmt::Error);
}
write!(f, "{} {units}", self.0)
}
#[allow(private_bounds)]
#[cfg(any(feature = "std", test))]
pub fn format_as(&self, units: &'static str) -> Result<std::string::String, FormatAsError> {
let si = match si_checked(units) {
Ok(v) => v,
Err(e) => return Err(FormatAsError::ParseError(e)),
};
if self.1 != si {
return Err(FormatAsError::IncompatibleUnits(InconsistentUnits {
expected: self.1,
found: si,
}));
}
Ok(format!("{} {units}", self.0))
}
}
#[cfg(feature = "const")]
impl<V, const UNITS: SI> TryFrom<DynQuantity<V>> for crate::Quantity<V, UNITS> {
type Error = InconsistentUnits;
fn try_from(value: DynQuantity<V>) -> Result<Self, Self::Error> {
if value.1 != UNITS {
return Err(InconsistentUnits {
expected: UNITS,
found: value.1,
});
}
Ok(crate::Quantity(value.0))
}
}
impl<V: PartialOrd> PartialOrd for DynQuantity<V> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
if self.1 != other.1 {
return None;
}
self.0.partial_cmp(&other.0)
}
}
impl<R, T: Add<R>> Add<DynQuantity<R>> for DynQuantity<T> {
type Output = DynQuantity<<T as Add<R>>::Output>;
fn add(self, rhs: DynQuantity<R>) -> Self::Output {
match self.checked_add(rhs) {
Ok(v) => v,
Err(e) => panic!("{e}"),
}
}
}
impl<R, T: Sub<R>> Sub<DynQuantity<R>> for DynQuantity<T> {
type Output = DynQuantity<<T as Sub<R>>::Output>;
fn sub(self, rhs: DynQuantity<R>) -> Self::Output {
match self.checked_sub(rhs) {
Ok(v) => v,
Err(e) => panic!("{e}"),
}
}
}
impl<R, T: AddAssign<R>> AddAssign<DynQuantity<R>> for DynQuantity<T> {
fn add_assign(&mut self, rhs: DynQuantity<R>) {
if self.1 != rhs.1 {
panic!(
"{}",
InconsistentUnits {
expected: self.1,
found: rhs.1
}
);
}
self.0 += rhs.0
}
}
impl<R, T: SubAssign<R>> SubAssign<DynQuantity<R>> for DynQuantity<T> {
fn sub_assign(&mut self, rhs: DynQuantity<R>) {
if self.1 != rhs.1 {
panic!(
"{}",
InconsistentUnits {
expected: self.1,
found: rhs.1
}
);
}
self.0 -= rhs.0
}
}
impl<R, T: MulAssign<R>> MulAssign<DynQuantity<R>> for DynQuantity<T> {
fn mul_assign(&mut self, rhs: DynQuantity<R>) {
if rhs.1 != DIMENSIONLESS {
panic!(
"{}",
InconsistentUnits {
expected: DIMENSIONLESS,
found: rhs.1,
}
);
}
self.0 *= rhs.0;
}
}
impl<R, T: DivAssign<R>> DivAssign<DynQuantity<R>> for DynQuantity<T> {
fn div_assign(&mut self, rhs: DynQuantity<R>) {
if rhs.1 != DIMENSIONLESS {
panic!(
"{}",
InconsistentUnits {
expected: DIMENSIONLESS,
found: rhs.1,
}
);
}
self.0 /= rhs.0;
}
}
impl<R, T: Mul<R>> Mul<DynQuantity<R>> for DynQuantity<T> {
type Output = DynQuantity<<T as Mul<R>>::Output>;
fn mul(self, rhs: DynQuantity<R>) -> Self::Output {
DynQuantity(self.0 * rhs.0, self.1.mul(rhs.1))
}
}
impl<R, T: Div<R>> Div<DynQuantity<R>> for DynQuantity<T> {
type Output = DynQuantity<<T as Div<R>>::Output>;
fn div(self, rhs: DynQuantity<R>) -> Self::Output {
DynQuantity(self.0 / rhs.0, self.1.div(rhs.1))
}
}