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}