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 }