qtty_core/
quantity.rs

1//! Quantity type and its implementations.
2
3use crate::unit::{Per, Unit};
4use core::marker::PhantomData;
5use core::ops::*;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10/// A quantity with a specific unit.
11///
12/// `Quantity<U>` wraps an `f64` value together with phantom type information
13/// about its unit `U`. This enables compile-time dimensional analysis while
14/// maintaining zero runtime cost.
15///
16/// # Examples
17///
18/// ```rust
19/// use qtty_core::{Quantity, Unit, Dimension};
20///
21/// pub enum Length {}
22/// impl Dimension for Length {}
23///
24/// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
25/// pub enum Meter {}
26/// impl Unit for Meter {
27///     const RATIO: f64 = 1.0;
28///     type Dim = Length;
29///     const SYMBOL: &'static str = "m";
30/// }
31///
32/// let x = Quantity::<Meter>::new(5.0);
33/// let y = Quantity::<Meter>::new(3.0);
34/// let sum = x + y;
35/// assert_eq!(sum.value(), 8.0);
36/// ```
37#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
38pub struct Quantity<U: Unit>(f64, PhantomData<U>);
39
40impl<U: Unit + Copy> Quantity<U> {
41    /// A constant representing NaN for this quantity type.
42    ///
43    /// ```rust
44    /// use qtty_core::length::Meters;
45    /// assert!(Meters::NAN.value().is_nan());
46    /// ```
47    pub const NAN: Self = Self::new(f64::NAN);
48
49    /// Creates a new quantity with the given value.
50    ///
51    /// ```rust
52    /// use qtty_core::length::Meters;
53    /// let d = Meters::new(3.0);
54    /// assert_eq!(d.value(), 3.0);
55    /// ```
56    #[inline]
57    pub const fn new(value: f64) -> Self {
58        Self(value, PhantomData)
59    }
60
61    /// Returns the raw numeric value.
62    ///
63    /// ```rust
64    /// use qtty_core::time::Seconds;
65    /// let t = Seconds::new(2.5);
66    /// assert_eq!(t.value(), 2.5);
67    /// ```
68    #[inline]
69    pub const fn value(self) -> f64 {
70        self.0
71    }
72
73    /// Returns the absolute value.
74    ///
75    /// ```rust
76    /// use qtty_core::angular::Degrees;
77    /// let a = Degrees::new(-10.0);
78    /// assert_eq!(a.abs().value(), 10.0);
79    /// ```
80    #[inline]
81    pub fn abs(self) -> Self {
82        Self::new(self.0.abs())
83    }
84
85    /// Converts this quantity to another unit of the same dimension.
86    ///
87    /// # Example
88    ///
89    /// ```rust
90    /// use qtty_core::{Quantity, Unit, Dimension};
91    ///
92    /// pub enum Length {}
93    /// impl Dimension for Length {}
94    ///
95    /// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
96    /// pub enum Meter {}
97    /// impl Unit for Meter {
98    ///     const RATIO: f64 = 1.0;
99    ///     type Dim = Length;
100    ///     const SYMBOL: &'static str = "m";
101    /// }
102    ///
103    /// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
104    /// pub enum Kilometer {}
105    /// impl Unit for Kilometer {
106    ///     const RATIO: f64 = 1000.0;
107    ///     type Dim = Length;
108    ///     const SYMBOL: &'static str = "km";
109    /// }
110    ///
111    /// let km = Quantity::<Kilometer>::new(1.0);
112    /// let m: Quantity<Meter> = km.to();
113    /// assert_eq!(m.value(), 1000.0);
114    /// ```
115    #[inline]
116    pub const fn to<T: Unit<Dim = U::Dim>>(self) -> Quantity<T> {
117        Quantity::<T>::new(self.0 * (U::RATIO / T::RATIO))
118    }
119
120    /// Returns the minimum of this quantity and another.
121    ///
122    /// ```rust
123    /// use qtty_core::length::Meters;
124    /// let a = Meters::new(3.0);
125    /// let b = Meters::new(5.0);
126    /// assert_eq!(a.min(b).value(), 3.0);
127    /// ```
128    #[inline]
129    pub const fn min(&self, other: Quantity<U>) -> Quantity<U> {
130        Quantity::<U>::new(self.value().min(other.value()))
131    }
132
133    /// Const addition of two quantities.
134    ///
135    /// ```rust
136    /// use qtty_core::length::Meters;
137    /// let a = Meters::new(1.0);
138    /// let b = Meters::new(2.0);
139    /// assert_eq!(a.add(b).value(), 3.0);
140    /// ```
141    #[inline]
142    pub const fn add(&self, other: Quantity<U>) -> Quantity<U> {
143        Quantity::<U>::new(self.value() + other.value())
144    }
145
146    /// Const subtraction of two quantities.
147    ///
148    /// ```rust
149    /// use qtty_core::length::Meters;
150    /// let a = Meters::new(5.0);
151    /// let b = Meters::new(2.0);
152    /// assert_eq!(a.sub(b).value(), 3.0);
153    /// ```
154    #[inline]
155    pub const fn sub(&self, other: Quantity<U>) -> Quantity<U> {
156        Quantity::<U>::new(self.value() - other.value())
157    }
158
159    /// Const division of two quantities (legacy behavior; returns the same unit).
160    ///
161    /// For a dimensionless ratio, prefer `/` (which yields a `Per<U, U>`) plus [`Simplify`].
162    ///
163    /// ```rust
164    /// use qtty_core::length::Meters;
165    /// let a = Meters::new(6.0);
166    /// let b = Meters::new(2.0);
167    /// assert_eq!(a.div(b).value(), 3.0);
168    /// ```
169    #[inline]
170    pub const fn div(&self, other: Quantity<U>) -> Quantity<U> {
171        Quantity::<U>::new(self.value() / other.value())
172    }
173
174    /// Const multiplication of two quantities (returns same unit).
175    ///
176    /// ```rust
177    /// use qtty_core::length::Meters;
178    /// let a = Meters::new(3.0);
179    /// let b = Meters::new(4.0);
180    /// assert_eq!(a.mul(b).value(), 12.0);
181    /// ```
182    #[inline]
183    pub const fn mul(&self, other: Quantity<U>) -> Quantity<U> {
184        Quantity::<U>::new(self.value() * other.value())
185    }
186}
187
188// ─────────────────────────────────────────────────────────────────────────────
189// Operator implementations
190// ─────────────────────────────────────────────────────────────────────────────
191
192impl<U: Unit> Add for Quantity<U> {
193    type Output = Self;
194    #[inline]
195    fn add(self, rhs: Self) -> Self {
196        Self::new(self.0 + rhs.0)
197    }
198}
199
200impl<U: Unit> AddAssign for Quantity<U> {
201    #[inline]
202    fn add_assign(&mut self, rhs: Self) {
203        self.0 += rhs.0;
204    }
205}
206
207impl<U: Unit> Sub for Quantity<U> {
208    type Output = Self;
209    #[inline]
210    fn sub(self, rhs: Self) -> Self {
211        Self::new(self.0 - rhs.0)
212    }
213}
214
215impl<U: Unit> SubAssign for Quantity<U> {
216    #[inline]
217    fn sub_assign(&mut self, rhs: Self) {
218        self.0 -= rhs.0;
219    }
220}
221
222impl<U: Unit> Mul<f64> for Quantity<U> {
223    type Output = Self;
224    #[inline]
225    fn mul(self, rhs: f64) -> Self {
226        Self::new(self.0 * rhs)
227    }
228}
229
230impl<U: Unit> Mul<Quantity<U>> for f64 {
231    type Output = Quantity<U>;
232    #[inline]
233    fn mul(self, rhs: Quantity<U>) -> Self::Output {
234        rhs * self
235    }
236}
237
238impl<U: Unit> Div<f64> for Quantity<U> {
239    type Output = Self;
240    #[inline]
241    fn div(self, rhs: f64) -> Self {
242        Self::new(self.0 / rhs)
243    }
244}
245
246impl<N: Unit, D: Unit> Mul<Quantity<D>> for Quantity<Per<N, D>> {
247    type Output = Quantity<N>;
248
249    #[inline]
250    fn mul(self, rhs: Quantity<D>) -> Self::Output {
251        Quantity::<N>::new(self.0 * rhs.value())
252    }
253}
254
255impl<N: Unit, D: Unit> Mul<Quantity<Per<N, D>>> for Quantity<D> {
256    type Output = Quantity<N>;
257
258    #[inline]
259    fn mul(self, rhs: Quantity<Per<N, D>>) -> Self::Output {
260        rhs * self
261    }
262}
263
264impl<U: Unit> DivAssign for Quantity<U> {
265    #[inline]
266    fn div_assign(&mut self, rhs: Self) {
267        self.0 /= rhs.0;
268    }
269}
270
271impl<U: Unit> Rem<f64> for Quantity<U> {
272    type Output = Self;
273    #[inline]
274    fn rem(self, rhs: f64) -> Self {
275        Self::new(self.0 % rhs)
276    }
277}
278
279impl<U: Unit> PartialEq<f64> for Quantity<U> {
280    #[inline]
281    fn eq(&self, other: &f64) -> bool {
282        self.0 == *other
283    }
284}
285
286impl<U: Unit> Neg for Quantity<U> {
287    type Output = Self;
288    #[inline]
289    fn neg(self) -> Self {
290        Self::new(-self.0)
291    }
292}
293
294impl<U: Unit> From<f64> for Quantity<U> {
295    #[inline]
296    fn from(value: f64) -> Self {
297        Self::new(value)
298    }
299}
300
301impl<N: Unit, D: Unit> Div<Quantity<D>> for Quantity<N> {
302    type Output = Quantity<Per<N, D>>;
303    #[inline]
304    fn div(self, rhs: Quantity<D>) -> Self::Output {
305        Quantity::new(self.value() / rhs.value())
306    }
307}
308
309// ─────────────────────────────────────────────────────────────────────────────
310// Special methods for Per<U, U> (unitless ratios)
311// ─────────────────────────────────────────────────────────────────────────────
312
313impl<U: Unit> Quantity<Per<U, U>> {
314    /// Arc sine of a unitless ratio.
315    ///
316    /// ```rust
317    /// use qtty_core::length::Meters;
318    /// let ratio = Meters::new(1.0) / Meters::new(2.0);
319    /// let angle_rad = ratio.asin();
320    /// assert!((angle_rad - core::f64::consts::FRAC_PI_6).abs() < 1e-12);
321    /// ```
322    #[inline]
323    pub fn asin(&self) -> f64 {
324        #[cfg(feature = "std")]
325        {
326            self.value().asin()
327        }
328        #[cfg(not(feature = "std"))]
329        {
330            libm::asin(self.value())
331        }
332    }
333}
334
335// ─────────────────────────────────────────────────────────────────────────────
336// Serde support
337// ─────────────────────────────────────────────────────────────────────────────
338
339#[cfg(feature = "serde")]
340impl<U: Unit> Serialize for Quantity<U> {
341    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
342    where
343        S: Serializer,
344    {
345        self.0.serialize(serializer)
346    }
347}
348
349#[cfg(feature = "serde")]
350impl<'de, U: Unit> Deserialize<'de> for Quantity<U> {
351    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
352    where
353        D: Deserializer<'de>,
354    {
355        let value = f64::deserialize(deserializer)?;
356        Ok(Quantity::new(value))
357    }
358}
359
360/// Serde helper module for serializing quantities with unit information.
361///
362/// Use this with the `#[serde(with = "...")]` attribute to preserve unit symbols
363/// in serialized data. This is useful for external APIs, configuration files, or
364/// self-documenting data formats.
365///
366/// # Examples
367///
368/// ```rust
369/// use qtty_core::length::Meters;
370/// use serde::{Serialize, Deserialize};
371///
372/// #[derive(Serialize, Deserialize)]
373/// struct Config {
374///     #[serde(with = "qtty_core::serde_with_unit")]
375///     max_distance: Meters,  // Serializes as {"value": 100.0, "unit": "m"}
376///     
377///     min_distance: Meters,  // Serializes as 50.0 (default, compact)
378/// }
379/// ```
380#[cfg(feature = "serde")]
381pub mod serde_with_unit {
382    use super::*;
383    use serde::de::{self, Deserializer, MapAccess, Visitor};
384    use serde::ser::{SerializeStruct, Serializer};
385
386    /// Serializes a `Quantity<U>` as a struct with `value` and `unit` fields.
387    ///
388    /// # Example JSON Output
389    /// ```json
390    /// {"value": 42.5, "unit": "m"}
391    /// ```
392    pub fn serialize<U, S>(quantity: &Quantity<U>, serializer: S) -> Result<S::Ok, S::Error>
393    where
394        U: Unit,
395        S: Serializer,
396    {
397        let mut state = serializer.serialize_struct("Quantity", 2)?;
398        state.serialize_field("value", &quantity.value())?;
399        state.serialize_field("unit", U::SYMBOL)?;
400        state.end()
401    }
402
403    /// Deserializes a `Quantity<U>` from a struct with `value` and optionally `unit` fields.
404    ///
405    /// The `unit` field is validated if present but not required for backwards compatibility.
406    /// If provided and doesn't match `U::SYMBOL`, a warning could be logged in the future.
407    pub fn deserialize<'de, U, D>(deserializer: D) -> Result<Quantity<U>, D::Error>
408    where
409        U: Unit,
410        D: Deserializer<'de>,
411    {
412        #[derive(Deserialize)]
413        #[serde(field_identifier, rename_all = "lowercase")]
414        enum Field {
415            Value,
416            Unit,
417        }
418
419        struct QuantityVisitor<U>(core::marker::PhantomData<U>);
420
421        impl<'de, U: Unit> Visitor<'de> for QuantityVisitor<U> {
422            type Value = Quantity<U>;
423
424            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
425                formatter.write_str("struct Quantity with value and unit fields")
426            }
427
428            fn visit_map<V>(self, mut map: V) -> Result<Quantity<U>, V::Error>
429            where
430                V: MapAccess<'de>,
431            {
432                let mut value: Option<f64> = None;
433                let mut unit: Option<String> = None;
434
435                while let Some(key) = map.next_key()? {
436                    match key {
437                        Field::Value => {
438                            if value.is_some() {
439                                return Err(de::Error::duplicate_field("value"));
440                            }
441                            value = Some(map.next_value()?);
442                        }
443                        Field::Unit => {
444                            if unit.is_some() {
445                                return Err(de::Error::duplicate_field("unit"));
446                            }
447                            unit = Some(map.next_value()?);
448                        }
449                    }
450                }
451
452                let value = value.ok_or_else(|| de::Error::missing_field("value"))?;
453
454                // Validate unit if provided (optional for backwards compatibility)
455                if let Some(ref unit_str) = unit {
456                    if unit_str != U::SYMBOL {
457                        return Err(de::Error::custom(format!(
458                            "unit mismatch: expected '{}', found '{}'",
459                            U::SYMBOL,
460                            unit_str
461                        )));
462                    }
463                }
464
465                Ok(Quantity::new(value))
466            }
467        }
468
469        deserializer.deserialize_struct(
470            "Quantity",
471            &["value", "unit"],
472            QuantityVisitor(core::marker::PhantomData),
473        )
474    }
475}