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}