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 = "tiberius")]
8use tiberius::{ColumnData, FromSql, ToSql};
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 a reference to the raw numeric value.
74    ///
75    /// This is useful for serialization and other operations that need
76    /// to borrow the value without consuming self.
77    #[inline]
78    pub const fn value_ref(&self) -> &f64 {
79        &self.0
80    }
81
82    /// Returns the absolute value.
83    ///
84    /// ```rust
85    /// use qtty_core::angular::Degrees;
86    /// let a = Degrees::new(-10.0);
87    /// assert_eq!(a.abs().value(), 10.0);
88    /// ```
89    #[inline]
90    pub fn abs(self) -> Self {
91        Self::new(self.0.abs())
92    }
93
94    /// Converts this quantity to another unit of the same dimension.
95    ///
96    /// # Example
97    ///
98    /// ```rust
99    /// use qtty_core::{Quantity, Unit, Dimension};
100    ///
101    /// pub enum Length {}
102    /// impl Dimension for Length {}
103    ///
104    /// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
105    /// pub enum Meter {}
106    /// impl Unit for Meter {
107    ///     const RATIO: f64 = 1.0;
108    ///     type Dim = Length;
109    ///     const SYMBOL: &'static str = "m";
110    /// }
111    ///
112    /// #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
113    /// pub enum Kilometer {}
114    /// impl Unit for Kilometer {
115    ///     const RATIO: f64 = 1000.0;
116    ///     type Dim = Length;
117    ///     const SYMBOL: &'static str = "km";
118    /// }
119    ///
120    /// let km = Quantity::<Kilometer>::new(1.0);
121    /// let m: Quantity<Meter> = km.to();
122    /// assert_eq!(m.value(), 1000.0);
123    /// ```
124    #[inline]
125    pub const fn to<T: Unit<Dim = U::Dim>>(self) -> Quantity<T> {
126        Quantity::<T>::new(self.0 * (U::RATIO / T::RATIO))
127    }
128
129    /// Returns the minimum of this quantity and another.
130    ///
131    /// ```rust
132    /// use qtty_core::length::Meters;
133    /// let a = Meters::new(3.0);
134    /// let b = Meters::new(5.0);
135    /// assert_eq!(a.min(b).value(), 3.0);
136    /// ```
137    #[inline]
138    pub const fn min(&self, other: Quantity<U>) -> Quantity<U> {
139        Quantity::<U>::new(self.value().min(other.value()))
140    }
141
142    /// Const addition of two quantities.
143    ///
144    /// ```rust
145    /// use qtty_core::length::Meters;
146    /// let a = Meters::new(1.0);
147    /// let b = Meters::new(2.0);
148    /// assert_eq!(a.add(b).value(), 3.0);
149    /// ```
150    #[inline]
151    pub const fn add(&self, other: Quantity<U>) -> Quantity<U> {
152        Quantity::<U>::new(self.value() + other.value())
153    }
154
155    /// Const subtraction of two quantities.
156    ///
157    /// ```rust
158    /// use qtty_core::length::Meters;
159    /// let a = Meters::new(5.0);
160    /// let b = Meters::new(2.0);
161    /// assert_eq!(a.sub(b).value(), 3.0);
162    /// ```
163    #[inline]
164    pub const fn sub(&self, other: Quantity<U>) -> Quantity<U> {
165        Quantity::<U>::new(self.value() - other.value())
166    }
167
168    /// Const division of two quantities (legacy behavior; returns the same unit).
169    ///
170    /// For a dimensionless ratio, prefer `/` (which yields a `Per<U, U>`) plus [`Simplify`].
171    ///
172    /// ```rust
173    /// use qtty_core::length::Meters;
174    /// let a = Meters::new(6.0);
175    /// let b = Meters::new(2.0);
176    /// assert_eq!(a.div(b).value(), 3.0);
177    /// ```
178    #[inline]
179    pub const fn div(&self, other: Quantity<U>) -> Quantity<U> {
180        Quantity::<U>::new(self.value() / other.value())
181    }
182
183    /// Const multiplication of two quantities (returns same unit).
184    ///
185    /// ```rust
186    /// use qtty_core::length::Meters;
187    /// let a = Meters::new(3.0);
188    /// let b = Meters::new(4.0);
189    /// assert_eq!(a.mul(b).value(), 12.0);
190    /// ```
191    #[inline]
192    pub const fn mul(&self, other: Quantity<U>) -> Quantity<U> {
193        Quantity::<U>::new(self.value() * other.value())
194    }
195}
196
197// ─────────────────────────────────────────────────────────────────────────────
198// Operator implementations
199// ─────────────────────────────────────────────────────────────────────────────
200
201impl<U: Unit> Add for Quantity<U> {
202    type Output = Self;
203    #[inline]
204    fn add(self, rhs: Self) -> Self {
205        Self::new(self.0 + rhs.0)
206    }
207}
208
209impl<U: Unit> AddAssign for Quantity<U> {
210    #[inline]
211    fn add_assign(&mut self, rhs: Self) {
212        self.0 += rhs.0;
213    }
214}
215
216impl<U: Unit> Sub for Quantity<U> {
217    type Output = Self;
218    #[inline]
219    fn sub(self, rhs: Self) -> Self {
220        Self::new(self.0 - rhs.0)
221    }
222}
223
224impl<U: Unit> SubAssign for Quantity<U> {
225    #[inline]
226    fn sub_assign(&mut self, rhs: Self) {
227        self.0 -= rhs.0;
228    }
229}
230
231impl<U: Unit> Mul<f64> for Quantity<U> {
232    type Output = Self;
233    #[inline]
234    fn mul(self, rhs: f64) -> Self {
235        Self::new(self.0 * rhs)
236    }
237}
238
239impl<U: Unit> Mul<Quantity<U>> for f64 {
240    type Output = Quantity<U>;
241    #[inline]
242    fn mul(self, rhs: Quantity<U>) -> Self::Output {
243        rhs * self
244    }
245}
246
247impl<U: Unit> Div<f64> for Quantity<U> {
248    type Output = Self;
249    #[inline]
250    fn div(self, rhs: f64) -> Self {
251        Self::new(self.0 / rhs)
252    }
253}
254
255impl<N: Unit, D: Unit> Mul<Quantity<D>> for Quantity<Per<N, D>> {
256    type Output = Quantity<N>;
257
258    #[inline]
259    fn mul(self, rhs: Quantity<D>) -> Self::Output {
260        Quantity::<N>::new(self.0 * rhs.value())
261    }
262}
263
264impl<N: Unit, D: Unit> Mul<Quantity<Per<N, D>>> for Quantity<D> {
265    type Output = Quantity<N>;
266
267    #[inline]
268    fn mul(self, rhs: Quantity<Per<N, D>>) -> Self::Output {
269        rhs * self
270    }
271}
272
273impl<U: Unit> DivAssign for Quantity<U> {
274    #[inline]
275    fn div_assign(&mut self, rhs: Self) {
276        self.0 /= rhs.0;
277    }
278}
279
280impl<U: Unit> Rem<f64> for Quantity<U> {
281    type Output = Self;
282    #[inline]
283    fn rem(self, rhs: f64) -> Self {
284        Self::new(self.0 % rhs)
285    }
286}
287
288impl<U: Unit> PartialEq<f64> for Quantity<U> {
289    #[inline]
290    fn eq(&self, other: &f64) -> bool {
291        self.0 == *other
292    }
293}
294
295impl<U: Unit> Neg for Quantity<U> {
296    type Output = Self;
297    #[inline]
298    fn neg(self) -> Self {
299        Self::new(-self.0)
300    }
301}
302
303impl<U: Unit> From<f64> for Quantity<U> {
304    #[inline]
305    fn from(value: f64) -> Self {
306        Self::new(value)
307    }
308}
309
310impl<N: Unit, D: Unit> Div<Quantity<D>> for Quantity<N> {
311    type Output = Quantity<Per<N, D>>;
312    #[inline]
313    fn div(self, rhs: Quantity<D>) -> Self::Output {
314        Quantity::new(self.value() / rhs.value())
315    }
316}
317
318// ─────────────────────────────────────────────────────────────────────────────
319// Special methods for Per<U, U> (unitless ratios)
320// ─────────────────────────────────────────────────────────────────────────────
321
322impl<U: Unit> Quantity<Per<U, U>> {
323    /// Arc sine of a unitless ratio.
324    ///
325    /// ```rust
326    /// use qtty_core::length::Meters;
327    /// let ratio = Meters::new(1.0) / Meters::new(2.0);
328    /// let angle_rad = ratio.asin();
329    /// assert!((angle_rad - core::f64::consts::FRAC_PI_6).abs() < 1e-12);
330    /// ```
331    #[inline]
332    pub fn asin(&self) -> f64 {
333        #[cfg(feature = "std")]
334        {
335            self.value().asin()
336        }
337        #[cfg(not(feature = "std"))]
338        {
339            libm::asin(self.value())
340        }
341    }
342}
343
344// ─────────────────────────────────────────────────────────────────────────────
345// Tiberius (SQL Server) database support
346// ─────────────────────────────────────────────────────────────────────────────
347
348#[cfg(feature = "tiberius")]
349impl<U: Unit + Send + Sync> ToSql for Quantity<U> {
350    fn to_sql(&self) -> ColumnData<'_> {
351        ColumnData::F64(Some(self.value()))
352    }
353}
354
355#[cfg(feature = "tiberius")]
356impl<U: Unit> FromSql<'_> for Quantity<U> {
357    fn from_sql(value: &ColumnData<'_>) -> tiberius::Result<Option<Self>> {
358        match value {
359            ColumnData::F64(Some(val)) => Ok(Some(Quantity::new(*val))),
360            ColumnData::F32(Some(val)) => Ok(Some(Quantity::new(*val as f64))),
361            ColumnData::I32(Some(val)) => Ok(Some(Quantity::new(*val as f64))),
362            ColumnData::I64(Some(val)) => Ok(Some(Quantity::new(*val as f64))),
363            _ => Ok(None),
364        }
365    }
366}