lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

quantity.rs (6524B)


      1 use core::fmt;
      2 
      3 use crate::RadrootsCoreDecimal;
      4 use crate::unit::{RadrootsCoreUnit, RadrootsCoreUnitConvertError, convert_unit_decimal};
      5 
      6 #[cfg(not(feature = "std"))]
      7 use alloc::string::String;
      8 #[cfg(feature = "std")]
      9 use std::string::String;
     10 
     11 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
     12 #[derive(Clone, Debug, PartialEq, Eq)]
     13 pub struct RadrootsCoreQuantity {
     14     #[cfg_attr(feature = "serde", serde(with = "crate::serde_ext::decimal_str"))]
     15     pub amount: RadrootsCoreDecimal,
     16     pub unit: RadrootsCoreUnit,
     17     #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
     18     pub label: Option<String>,
     19 }
     20 
     21 impl RadrootsCoreQuantity {
     22     #[inline]
     23     pub fn new(amount: RadrootsCoreDecimal, unit: RadrootsCoreUnit) -> Self {
     24         Self {
     25             amount,
     26             unit,
     27             label: None,
     28         }
     29     }
     30 
     31     #[inline]
     32     pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
     33         self.label = Some(label.into());
     34         self
     35     }
     36 
     37     #[inline]
     38     pub fn with_optional_label<S: Into<String>>(mut self, label: Option<S>) -> Self {
     39         self.label = label.map(|s| s.into());
     40         self
     41     }
     42 
     43     #[inline]
     44     pub fn clear_label(mut self) -> Self {
     45         self.label = None;
     46         self
     47     }
     48 
     49     #[inline]
     50     pub fn zero(unit: RadrootsCoreUnit) -> Self {
     51         Self {
     52             amount: RadrootsCoreDecimal::ZERO,
     53             unit,
     54             label: None,
     55         }
     56     }
     57 
     58     #[inline]
     59     pub fn is_zero(&self) -> bool {
     60         self.amount.is_zero()
     61     }
     62 
     63     #[inline]
     64     pub fn is_canonical(&self) -> bool {
     65         self.unit == self.unit.canonical_unit()
     66     }
     67 
     68     #[inline]
     69     pub fn canonical_unit(&self) -> RadrootsCoreUnit {
     70         self.unit.canonical_unit()
     71     }
     72 
     73     #[inline]
     74     pub fn try_convert_to(
     75         &self,
     76         unit: RadrootsCoreUnit,
     77     ) -> Result<RadrootsCoreQuantity, RadrootsCoreUnitConvertError> {
     78         if self.unit == unit {
     79             return Ok(self.clone());
     80         }
     81         let amount = convert_unit_decimal(self.amount, self.unit, unit)?;
     82         Ok(RadrootsCoreQuantity {
     83             amount,
     84             unit,
     85             label: self.label.clone(),
     86         })
     87     }
     88 
     89     #[inline]
     90     pub fn to_canonical(&self) -> Result<RadrootsCoreQuantity, RadrootsCoreUnitConvertError> {
     91         self.try_convert_to(self.unit.canonical_unit())
     92     }
     93 
     94     #[inline]
     95     pub fn ensure_non_negative(&self) -> Result<(), RadrootsCoreQuantityInvariantError> {
     96         if self.amount.is_sign_negative() {
     97             return Err(RadrootsCoreQuantityInvariantError::NegativeAmount);
     98         }
     99         Ok(())
    100     }
    101 
    102     #[inline]
    103     pub fn with_scale(mut self, scale: u32) -> Self {
    104         self.amount.rescale(scale);
    105         self
    106     }
    107 
    108     #[inline]
    109     pub fn try_add(
    110         &self,
    111         rhs: &RadrootsCoreQuantity,
    112     ) -> Result<RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError> {
    113         if self.unit != rhs.unit {
    114             return Err(RadrootsCoreQuantityInvariantError::UnitMismatch);
    115         }
    116         Ok(RadrootsCoreQuantity {
    117             amount: self.amount + rhs.amount,
    118             unit: self.unit,
    119             label: self.label.clone(),
    120         })
    121     }
    122 
    123     #[inline]
    124     pub fn try_sub(
    125         &self,
    126         rhs: &RadrootsCoreQuantity,
    127     ) -> Result<RadrootsCoreQuantity, RadrootsCoreQuantityInvariantError> {
    128         if self.unit != rhs.unit {
    129             return Err(RadrootsCoreQuantityInvariantError::UnitMismatch);
    130         }
    131         Ok(RadrootsCoreQuantity {
    132             amount: self.amount - rhs.amount,
    133             unit: self.unit,
    134             label: self.label.clone(),
    135         })
    136     }
    137 
    138     pub fn checked_add(&self, rhs: &RadrootsCoreQuantity) -> Option<RadrootsCoreQuantity> {
    139         if self.unit == rhs.unit {
    140             Some(RadrootsCoreQuantity {
    141                 amount: self.amount + rhs.amount,
    142                 unit: self.unit,
    143                 label: self.label.clone(),
    144             })
    145         } else {
    146             None
    147         }
    148     }
    149 
    150     pub fn checked_sub(&self, rhs: &RadrootsCoreQuantity) -> Option<RadrootsCoreQuantity> {
    151         if self.unit == rhs.unit {
    152             Some(RadrootsCoreQuantity {
    153                 amount: self.amount - rhs.amount,
    154                 unit: self.unit,
    155                 label: self.label.clone(),
    156             })
    157         } else {
    158             None
    159         }
    160     }
    161 
    162     #[inline]
    163     pub fn mul_decimal(&self, factor: RadrootsCoreDecimal) -> RadrootsCoreQuantity {
    164         RadrootsCoreQuantity {
    165             amount: self.amount * factor,
    166             unit: self.unit,
    167             label: self.label.clone(),
    168         }
    169     }
    170 
    171     #[inline]
    172     pub fn div_decimal(&self, divisor: RadrootsCoreDecimal) -> RadrootsCoreQuantity {
    173         RadrootsCoreQuantity {
    174             amount: self.amount / divisor,
    175             unit: self.unit,
    176             label: self.label.clone(),
    177         }
    178     }
    179 }
    180 
    181 impl fmt::Display for RadrootsCoreQuantity {
    182     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    183         write!(f, "{} {}", self.amount.normalize(), self.unit)?;
    184         if let Some(label) = &self.label {
    185             write!(f, " ({label})")?;
    186         }
    187         Ok(())
    188     }
    189 }
    190 
    191 #[non_exhaustive]
    192 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    193 pub enum RadrootsCoreQuantityInvariantError {
    194     NegativeAmount,
    195     UnitMismatch,
    196 }
    197 
    198 impl fmt::Display for RadrootsCoreQuantityInvariantError {
    199     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    200         match self {
    201             RadrootsCoreQuantityInvariantError::NegativeAmount => {
    202                 write!(f, "quantity amount must be ≥ 0")
    203             }
    204             RadrootsCoreQuantityInvariantError::UnitMismatch => {
    205                 write!(f, "quantity unit mismatch")
    206             }
    207         }
    208     }
    209 }
    210 
    211 #[cfg(feature = "std")]
    212 impl std::error::Error for RadrootsCoreQuantityInvariantError {}
    213 
    214 use core::ops::{Div, Mul};
    215 
    216 impl Mul<RadrootsCoreDecimal> for RadrootsCoreQuantity {
    217     type Output = RadrootsCoreQuantity;
    218     fn mul(self, rhs: RadrootsCoreDecimal) -> RadrootsCoreQuantity {
    219         RadrootsCoreQuantity {
    220             amount: self.amount * rhs,
    221             unit: self.unit,
    222             label: self.label,
    223         }
    224     }
    225 }
    226 
    227 impl Div<RadrootsCoreDecimal> for RadrootsCoreQuantity {
    228     type Output = RadrootsCoreQuantity;
    229     fn div(self, rhs: RadrootsCoreDecimal) -> RadrootsCoreQuantity {
    230         RadrootsCoreQuantity {
    231             amount: self.amount / rhs,
    232             unit: self.unit,
    233             label: self.label,
    234         }
    235     }
    236 }