Skip to main content

qtty_core/
quantity.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Quantity type and its implementations.
5
6use crate::scalar::{Exact, Real, Scalar, Transcendental};
7use crate::unit::Unit;
8use crate::unit_arithmetic::{QuantityDivOutput, UnitDiv, UnitMul, UnitSqrt};
9use core::cmp::Ordering;
10use core::hash::{Hash, Hasher};
11use core::iter::Sum;
12use core::marker::PhantomData;
13use core::ops::*;
14
15/// A quantity with a specific unit and scalar type.
16///
17/// `Quantity<U, S>` wraps a scalar value of type `S` together with phantom type
18/// information about its unit `U`. This enables compile-time dimensional analysis
19/// while maintaining zero runtime cost beyond the scalar's size.
20///
21/// The default scalar type is `f64`, so `Quantity<Meter>` is equivalent to
22/// `Quantity<Meter, f64>`.
23///
24/// # Examples
25///
26/// Basic usage with default `f64`:
27///
28/// ```rust
29/// use qtty_core::length::{Meter, Meters};
30/// use qtty_core::Quantity;
31///
32/// let x = Meters::new(5.0);
33/// let y = Meters::new(3.0);
34/// let sum = x + y;
35/// assert_eq!(sum.value(), 8.0);
36/// ```
37///
38/// Using `f32` for memory efficiency:
39///
40/// ```rust
41/// use qtty_core::length::Meter;
42/// use qtty_core::Quantity;
43///
44/// let x: Quantity<Meter, f32> = Quantity::new(5.0_f32);
45/// assert_eq!(x.value(), 5.0_f32);
46/// ```
47#[repr(transparent)]
48#[derive(Clone, Copy, Debug, PartialEq)]
49pub struct Quantity<U: Unit, S: Scalar = f64>(S, PhantomData<U>);
50
51// ─────────────────────────────────────────────────────────────────────────────
52// Type aliases for common scalar types
53// ─────────────────────────────────────────────────────────────────────────────
54
55/// A quantity backed by `f64` (the default).
56pub type Quantity64<U> = Quantity<U, f64>;
57
58/// A quantity backed by `f32`.
59pub type Quantity32<U> = Quantity<U, f32>;
60
61/// A quantity backed by `num_rational::Rational64`.
62#[cfg(feature = "scalar-rational")]
63pub type QuantityRational<U> = Quantity<U, num_rational::Rational64>;
64
65/// A quantity backed by `i8`.
66pub type QuantityI8<U> = Quantity<U, i8>;
67
68/// A quantity backed by `i16`.
69pub type QuantityI16<U> = Quantity<U, i16>;
70
71/// A quantity backed by `i32`.
72pub type QuantityI32<U> = Quantity<U, i32>;
73
74/// A quantity backed by `i64`.
75pub type QuantityI64<U> = Quantity<U, i64>;
76
77/// A quantity backed by `i128`.
78pub type QuantityI128<U> = Quantity<U, i128>;
79
80/// A quantity backed by `u32`.
81pub type QuantityU32<U> = Quantity<U, u32>;
82
83// ─────────────────────────────────────────────────────────────────────────────
84// Core implementation for all Scalar types
85// ─────────────────────────────────────────────────────────────────────────────
86
87impl<U: Unit, S: Scalar> Quantity<U, S> {
88    /// Creates a new quantity with the given value.
89    ///
90    /// ```rust
91    /// use qtty_core::length::Meters;
92    /// let d = Meters::new(3.0);
93    /// assert_eq!(d.value(), 3.0);
94    /// ```
95    #[inline]
96    pub const fn new(value: S) -> Self {
97        Self(value, PhantomData)
98    }
99
100    /// Returns the raw numeric value.
101    ///
102    /// ```rust
103    /// use qtty_core::time::Seconds;
104    /// let t = Seconds::new(2.5);
105    /// assert_eq!(t.value(), 2.5);
106    /// ```
107    #[inline]
108    pub const fn value(self) -> S {
109        self.0
110    }
111
112    /// Returns a reference to the raw numeric value.
113    #[inline]
114    pub const fn value_ref(&self) -> &S {
115        &self.0
116    }
117
118    /// Returns the absolute value.
119    ///
120    /// ```rust
121    /// use qtty_core::angular::Degrees;
122    /// let a = Degrees::new(-10.0);
123    /// assert_eq!(a.abs().value(), 10.0);
124    /// ```
125    #[inline]
126    pub fn abs(self) -> Self {
127        Self::new(self.0.abs())
128    }
129
130    /// Returns the minimum of this quantity and another.
131    ///
132    /// ```rust
133    /// use qtty_core::length::Meters;
134    /// let a = Meters::new(3.0);
135    /// let b = Meters::new(5.0);
136    /// assert_eq!(a.min(b).value(), 3.0);
137    /// ```
138    #[inline]
139    pub fn min(self, other: Self) -> Self {
140        Self::new(self.0.min(other.0))
141    }
142
143    /// Returns the maximum of this quantity and another.
144    #[inline]
145    pub fn max(self, other: Self) -> Self {
146        Self::new(self.0.max(other.0))
147    }
148
149    /// Clamps this quantity to `[min_val, max_val]`.
150    #[inline]
151    pub fn clamp(self, min_val: Self, max_val: Self) -> Self {
152        debug_assert!(
153            min_val.0 <= max_val.0,
154            "Quantity::clamp requires min_val <= max_val"
155        );
156        self.max(min_val).min(max_val)
157    }
158
159    /// Returns the arithmetic mean (midpoint) of this quantity and another.
160    ///
161    /// For integer-backed quantities this uses integer division semantics
162    /// (truncation toward zero). The computation is overflow-safe for all
163    /// scalar types, including integers at their extremes.
164    ///
165    /// ```rust
166    /// use qtty_core::length::Meters;
167    /// let a = Meters::new(10.0);
168    /// let b = Meters::new(14.0);
169    /// assert_eq!(a.mean(b).value(), 12.0);
170    /// ```
171    #[inline]
172    pub fn mean(self, other: Self) -> Self {
173        let two = S::ONE + S::ONE;
174        let a = self.0;
175        let b = other.0;
176        // Equal operands: the midpoint is the operand itself.
177        // This also short-circuits same-sign infinities (e.g. +∞.mean(+∞)):
178        // the split-half path below computes ∞ − ∞ = NaN for those cases,
179        // which would violate the IEEE-754 expectation that the midpoint of
180        // two identical infinities stays infinite.
181        if a == b {
182            return Self::new(a);
183        }
184        // When both values have the same sign, their sum may overflow.
185        // Use a split-half formula that is safe for same-sign operands.
186        // When signs differ, the direct sum never overflows (for integers
187        // it stays within the type's range; for floats it is always fine).
188        if (a >= S::ZERO) == (b >= S::ZERO) {
189            let ha = a / two;
190            let hb = b / two;
191            // For ±∞: a / 2 == a (infinity halved is still infinity), so
192            // `a - ha * two` would compute ∞ − ∞ = NaN.  Treat the
193            // remainder as zero in that case; the half already carries the
194            // full infinite magnitude.
195            let ra = if ha == a { S::ZERO } else { a - ha * two };
196            let rb = if hb == b { S::ZERO } else { b - hb * two };
197            Self::new(ha + hb + (ra + rb) / two)
198        } else {
199            Self::new((a + b) / two)
200        }
201    }
202
203    /// A constant representing the zero value for this quantity type.
204    #[inline]
205    pub const fn zero() -> Self {
206        Self::new(S::ZERO)
207    }
208
209    /// A constant representing the unit value (one) for this quantity type.
210    #[inline]
211    pub const fn one() -> Self {
212        Self::new(S::ONE)
213    }
214
215    /// Erases the unit tag, returning the raw stored scalar `S`.
216    ///
217    /// **This is a lossy operation**: the raw stored number is returned as-is,
218    /// without any normalization to the canonical (SI) unit. Use this only
219    /// when you explicitly intend to discard dimensional information, e.g.
220    /// for adapter layers or debugging.
221    ///
222    /// For a true dimensionless ratio, divide two quantities of the same unit
223    /// instead (`a / b` where both are `Quantity<U, S>`).
224    ///
225    /// # Example
226    ///
227    /// ```rust
228    /// use qtty_core::length::Kilometers;
229    ///
230    /// let km = Kilometers::new(1.0);
231    /// let raw: f64 = km.erase_unit_raw();
232    /// assert_eq!(raw, 1.0); // raw stored number, NOT 1000.0
233    /// ```
234    #[inline]
235    pub fn erase_unit_raw(self) -> S {
236        self.0
237    }
238}
239
240// ─────────────────────────────────────────────────────────────────────────────
241// Real-specific implementations (f32, f64, etc.)
242// ─────────────────────────────────────────────────────────────────────────────
243
244impl<U: Unit, S: Real> Quantity<U, S> {
245    /// A constant representing NaN for this quantity type.
246    ///
247    /// Note: For scalar types without a NaN representation, this may be an
248    /// approximation rather than a true IEEE-754 NaN.
249    ///
250    /// ```rust
251    /// use qtty_core::length::Meters;
252    /// assert!(Meters::NAN.value().is_nan());
253    /// ```
254    pub const NAN: Self = Self(S::NAN, PhantomData);
255
256    /// A constant representing positive infinity.
257    pub const INFINITY: Self = Self(S::INFINITY, PhantomData);
258
259    /// A constant representing negative infinity.
260    pub const NEG_INFINITY: Self = Self(S::NEG_INFINITY, PhantomData);
261
262    /// Returns true if the value is NaN.
263    #[inline]
264    pub fn is_nan(self) -> bool {
265        self.0.is_nan()
266    }
267
268    /// Returns true if the value is infinite.
269    #[inline]
270    pub fn is_infinite(self) -> bool {
271        self.0.is_infinite()
272    }
273
274    /// Returns true if the value is finite.
275    #[inline]
276    pub fn is_finite(self) -> bool {
277        self.0.is_finite()
278    }
279
280    /// Converts this quantity to another unit of the same dimension.
281    ///
282    /// The conversion multiplies the scalar value by `U::RATIO / T::RATIO`.
283    /// Because [`Unit::RATIO`] is always an `f64`, this ratio is computed in
284    /// `f64` and then cast back to `S` via [`Scalar::from_f64`].  For
285    /// floating-point scalars (`f64`, `f32`) the precision loss is negligible.
286    /// For **exact scalar types** (`Rational64`, integers) the cast is lossy:
287    /// the numeric value will be an `f64`-rounded approximation of the true
288    /// rational ratio.  See [`Exact`] for the explicit trade-off documentation.
289    ///
290    /// If you need unit conversion without the `f64` round-trip, store values
291    /// in the target unit from the start rather than converting after the fact.
292    ///
293    /// # Example
294    ///
295    /// ```rust
296    /// use qtty_core::length::{Meter, Kilometer, Kilometers};
297    /// use qtty_core::Quantity;
298    ///
299    /// let km = Kilometers::new(1.0);
300    /// let m: Quantity<Meter> = km.to();
301    /// assert_eq!(m.value(), 1000.0);
302    /// ```
303    #[inline]
304    pub fn to<T: Unit<Dim = U::Dim>>(self) -> Quantity<T, S> {
305        let ratio = S::from_f64(U::RATIO / T::RATIO);
306        Quantity::<T, S>::new(self.0 * ratio)
307    }
308
309    /// Convert the scalar type while preserving the unit.
310    ///
311    /// This converts via `f64`, so precision may be lost for types with
312    /// higher precision than `f64`.
313    ///
314    /// # Example
315    ///
316    /// ```rust
317    /// use qtty_core::length::{Meter, Meters};
318    /// use qtty_core::Quantity;
319    ///
320    /// let meters_f64 = Meters::new(100.0);
321    /// let meters_f32: Quantity<Meter, f32> = meters_f64.cast();
322    /// assert_eq!(meters_f32.value(), 100.0_f32);
323    /// ```
324    #[inline]
325    pub fn cast<T: Real>(self) -> Quantity<U, T> {
326        Quantity::new(T::from_f64(self.0.to_f64()))
327    }
328
329    /// Sign of the value.
330    #[inline]
331    pub fn signum(self) -> S {
332        self.0.signum()
333    }
334
335    /// Returns the square root of the underlying scalar value.
336    ///
337    /// This returns the raw scalar `S` rather than `Quantity<U, S>`, because
338    /// the square root of a dimensional quantity does not in general carry the
339    /// same dimension (e.g. √(m²) = m, not m²).  If you need a quantity
340    /// result, wrap it explicitly with the correct unit type.
341    #[inline]
342    pub fn scalar_sqrt(self) -> S {
343        self.0.sqrt()
344    }
345
346    /// Returns the largest integer quantity less than or equal to this value.
347    #[inline]
348    pub fn floor(self) -> Self {
349        Self::new(self.0.floor())
350    }
351
352    /// Returns the smallest integer quantity greater than or equal to this value.
353    #[inline]
354    pub fn ceil(self) -> Self {
355        Self::new(self.0.ceil())
356    }
357
358    /// Returns the nearest integer quantity to this value.
359    #[inline]
360    pub fn round(self) -> Self {
361        Self::new(self.0.round())
362    }
363
364    /// Returns the integer part of this quantity.
365    #[inline]
366    pub fn trunc(self) -> Self {
367        Self::new(self.0.trunc())
368    }
369
370    /// Returns the fractional part of this quantity.
371    #[inline]
372    pub fn fract(self) -> Self {
373        Self::new(self.0.fract())
374    }
375
376    /// Checks equality with a quantity of a different unit in the same dimension.
377    ///
378    /// Both operands are converted to the reference (SI) unit before comparison,
379    /// ensuring that `a.eq_unit(&b)` and `b.eq_unit(&a)` always agree.
380    ///
381    /// Note that floating-point conversion may introduce rounding; for exact
382    /// equality checks consider using an epsilon tolerance.
383    ///
384    /// # Example
385    ///
386    /// ```rust
387    /// use qtty_core::length::{Kilometers, Meters};
388    ///
389    /// let km = Kilometers::new(1.0);
390    /// let m = Meters::new(1000.0);
391    /// assert!(km.eq_unit(&m));
392    /// ```
393    #[inline]
394    pub fn eq_unit<V: Unit<Dim = U::Dim>>(self, other: &Quantity<V, S>) -> bool {
395        // Always multiply the value in the smaller-RATIO unit by the ratio
396        // (smaller/larger) ≤ 1, preventing overflow for near-MAX values while
397        // keeping both `a.eq_unit(&b)` and `b.eq_unit(&a)` numerically
398        // identical (preserving symmetry).
399        if U::RATIO >= V::RATIO {
400            self.0 == other.value() * S::from_f64(V::RATIO / U::RATIO)
401        } else {
402            self.0 * S::from_f64(U::RATIO / V::RATIO) == other.value()
403        }
404    }
405
406    /// Compares with a quantity of a different unit in the same dimension.
407    ///
408    /// Both operands are converted to the reference (SI) unit before comparison,
409    /// ensuring order-consistency regardless of operand direction.
410    ///
411    /// # Example
412    ///
413    /// ```rust
414    /// use qtty_core::length::{Kilometers, Meters};
415    /// use core::cmp::Ordering;
416    ///
417    /// let km = Kilometers::new(2.0);
418    /// let m = Meters::new(500.0);
419    /// assert_eq!(km.cmp_unit(&m), Some(Ordering::Greater));
420    /// ```
421    #[inline]
422    pub fn cmp_unit<V: Unit<Dim = U::Dim>>(self, other: &Quantity<V, S>) -> Option<Ordering> {
423        if U::RATIO >= V::RATIO {
424            self.0
425                .partial_cmp(&(other.value() * S::from_f64(V::RATIO / U::RATIO)))
426        } else {
427            (self.0 * S::from_f64(U::RATIO / V::RATIO)).partial_cmp(&other.value())
428        }
429    }
430}
431
432// ─────────────────────────────────────────────────────────────────────────────
433// Exact-specific implementations (integers, rationals, etc.)
434// ─────────────────────────────────────────────────────────────────────────────
435
436impl<U: Unit, S: Exact> Quantity<U, S> {
437    /// Converts this quantity to another unit of the same dimension (lossy).
438    ///
439    /// For integer scalars this performs the conversion through `f64` intermediate
440    /// arithmetic, then truncates back to the integer type. The result may lose
441    /// precision due to:
442    ///
443    /// - **Truncation toward zero** for fractional results (e.g. `1500 m → 1 km`).
444    /// - **Saturation at integer bounds** when the converted value exceeds the
445    ///   target type's range (e.g. `1 km → 127 m` for `i8`).
446    ///
447    /// # When to use which variant
448    ///
449    /// | Situation | Recommended API |
450    /// |-----------|-----------------|
451    /// | Overflow is impossible by domain invariant | `to_lossy` |
452    /// | Overflow is possible / value is untrusted | [`checked_to_lossy`](Self::checked_to_lossy) |
453    ///
454    /// Prefer [`checked_to_lossy`](Self::checked_to_lossy) whenever the input
455    /// value comes from external data or a computation that might produce a
456    /// value outside the target type's range.  Silent saturation is an easy
457    /// source of subtle bugs in integer-heavy code.
458    ///
459    /// # Example
460    ///
461    /// ```rust
462    /// use qtty_core::Quantity;
463    /// use qtty_core::length::{Meter, Kilometer};
464    ///
465    /// let m: Quantity<Meter, i32> = Quantity::new(1500);
466    /// let km: Quantity<Kilometer, i32> = m.to_lossy();
467    /// assert_eq!(km.value(), 1); // truncated from 1.5
468    /// ```
469    #[inline]
470    pub fn to_lossy<T: Unit<Dim = U::Dim>>(self) -> Quantity<T, S> {
471        let ratio = U::RATIO / T::RATIO;
472        // Same-ratio fast path: skip the f64 round-trip entirely.
473        // Without this, large integer values (e.g. near i64::MAX) would be
474        // corrupted even for identity conversions because f64 cannot represent
475        // them exactly.
476        if ratio == 1.0 {
477            return Quantity::<T, S>::new(self.0);
478        }
479        let value_f64 = self.0.to_f64_approx();
480        Quantity::<T, S>::new(S::from_f64_approx(value_f64 * ratio))
481    }
482
483    /// Checked lossy unit conversion.
484    ///
485    /// Like [`to_lossy`](Self::to_lossy), but returns `None` when the converted
486    /// value would overflow the scalar type (i.e. saturation/clipping would
487    /// occur). Fractional truncation toward zero is still permitted.
488    ///
489    /// # Example
490    ///
491    /// ```rust
492    /// use qtty_core::Quantity;
493    /// use qtty_core::length::{Meter, Kilometer};
494    ///
495    /// let km: Quantity<Kilometer, i8> = Quantity::new(1);
496    /// // 1 km = 1000 m, which doesn't fit in i8
497    /// assert_eq!(km.checked_to_lossy::<Meter>(), None);
498    ///
499    /// let m: Quantity<Meter, i32> = Quantity::new(1500);
500    /// let km: Option<Quantity<Kilometer, i32>> = m.checked_to_lossy();
501    /// assert_eq!(km.unwrap().value(), 1); // truncated, but within range
502    /// ```
503    #[inline]
504    pub fn checked_to_lossy<T: Unit<Dim = U::Dim>>(self) -> Option<Quantity<T, S>> {
505        let ratio = U::RATIO / T::RATIO;
506        // Same-ratio fast path: the value is unchanged, so always in range.
507        // Without this, large integers near i64::MAX would round-trip through
508        // f64 and either return a mutated value or a false None.
509        if ratio == 1.0 {
510            return Some(Quantity::<T, S>::new(self.0));
511        }
512        let value_f64 = self.0.to_f64_approx();
513        S::checked_from_f64(value_f64 * ratio).map(Quantity::<T, S>::new)
514    }
515}
516
517// ─────────────────────────────────────────────────────────────────────────────
518// Const methods for f64 (backward compatibility)
519// ─────────────────────────────────────────────────────────────────────────────
520
521impl<U: Unit + Copy> Quantity<U, f64> {
522    /// Const addition of two quantities.
523    ///
524    /// ```rust
525    /// use qtty_core::length::Meters;
526    /// let a = Meters::new(1.0);
527    /// let b = Meters::new(2.0);
528    /// assert_eq!(a.const_add(b).value(), 3.0);
529    /// ```
530    #[inline]
531    pub const fn const_add(self, other: Self) -> Self {
532        Self(self.0 + other.0, PhantomData)
533    }
534
535    /// Const subtraction of two quantities.
536    #[inline]
537    pub const fn const_sub(self, other: Self) -> Self {
538        Self(self.0 - other.0, PhantomData)
539    }
540
541    /// Const multiplication by a scalar.
542    #[inline]
543    pub const fn const_mul(self, rhs: f64) -> Self {
544        Self(self.0 * rhs, PhantomData)
545    }
546
547    /// Const division by a scalar.
548    #[inline]
549    pub const fn const_div(self, rhs: f64) -> Self {
550        Self(self.0 / rhs, PhantomData)
551    }
552
553    /// Const conversion to another unit.
554    #[inline]
555    pub const fn to_const<T: Unit<Dim = U::Dim> + Copy>(self) -> Quantity<T, f64> {
556        Quantity::<T, f64>(self.0 * (U::RATIO / T::RATIO), PhantomData)
557    }
558
559    /// Const min of two quantities.
560    #[inline]
561    pub const fn min_const(self, other: Self) -> Self {
562        if self.0 < other.0 {
563            self
564        } else {
565            other
566        }
567    }
568
569    /// Const max of two quantities.
570    #[inline]
571    pub const fn max_const(self, other: Self) -> Self {
572        if self.0 > other.0 {
573            self
574        } else {
575            other
576        }
577    }
578
579    /// Returns the least non-negative remainder of `self.value() % rhs`.
580    ///
581    /// Equivalent to `f64::rem_euclid`: always returns a value in `[0, rhs)` for `rhs > 0`.
582    /// Useful for canonicalising angular quantities to `[0°, 360°)` without extracting the inner value.
583    #[inline]
584    pub fn rem_euclid(self, rhs: f64) -> Self {
585        Self::new(self.0.rem_euclid(rhs))
586    }
587}
588
589// ─────────────────────────────────────────────────────────────────────────────
590// Const methods for f32
591// ─────────────────────────────────────────────────────────────────────────────
592
593impl<U: Unit + Copy> Quantity<U, f32> {
594    /// Const addition of two quantities.
595    #[inline]
596    pub const fn const_add(self, other: Self) -> Self {
597        Self(self.0 + other.0, PhantomData)
598    }
599
600    /// Const subtraction of two quantities.
601    #[inline]
602    pub const fn const_sub(self, other: Self) -> Self {
603        Self(self.0 - other.0, PhantomData)
604    }
605
606    /// Const multiplication by a scalar.
607    #[inline]
608    pub const fn const_mul(self, rhs: f32) -> Self {
609        Self(self.0 * rhs, PhantomData)
610    }
611
612    /// Const division by a scalar.
613    #[inline]
614    pub const fn const_div(self, rhs: f32) -> Self {
615        Self(self.0 / rhs, PhantomData)
616    }
617
618    /// Const conversion to another unit.
619    #[inline]
620    pub const fn to_const<T: Unit<Dim = U::Dim> + Copy>(self) -> Quantity<T, f32> {
621        Quantity::<T, f32>(self.0 * ((U::RATIO / T::RATIO) as f32), PhantomData)
622    }
623
624    /// Const min of two quantities.
625    #[inline]
626    pub const fn min_const(self, other: Self) -> Self {
627        if self.0 < other.0 {
628            self
629        } else {
630            other
631        }
632    }
633
634    /// Const max of two quantities.
635    #[inline]
636    pub const fn max_const(self, other: Self) -> Self {
637        if self.0 > other.0 {
638            self
639        } else {
640            other
641        }
642    }
643}
644
645// ─────────────────────────────────────────────────────────────────────────────
646// Const methods for integer types
647// ─────────────────────────────────────────────────────────────────────────────
648
649macro_rules! impl_const_for_int {
650    ($($t:ty),*) => { $(
651        impl<U: Unit + Copy> Quantity<U, $t> {
652            /// Const addition of two quantities.
653            #[inline]
654            pub const fn const_add(self, other: Self) -> Self {
655                Self(self.0 + other.0, PhantomData)
656            }
657
658            /// Const subtraction of two quantities.
659            #[inline]
660            pub const fn const_sub(self, other: Self) -> Self {
661                Self(self.0 - other.0, PhantomData)
662            }
663
664            /// Const multiplication by a scalar.
665            #[inline]
666            pub const fn const_mul(self, rhs: $t) -> Self {
667                Self(self.0 * rhs, PhantomData)
668            }
669
670            /// Const division by a scalar.
671            #[inline]
672            pub const fn const_div(self, rhs: $t) -> Self {
673                Self(self.0 / rhs, PhantomData)
674            }
675
676            /// Const min of two quantities.
677            #[inline]
678            pub const fn min_const(self, other: Self) -> Self {
679                if self.0 < other.0 {
680                    self
681                } else {
682                    other
683                }
684            }
685
686            /// Const max of two quantities.
687            #[inline]
688            pub const fn max_const(self, other: Self) -> Self {
689                if self.0 > other.0 {
690                    self
691                } else {
692                    other
693                }
694            }
695        }
696    )* };
697}
698
699impl_const_for_int!(i8, i16, i32, i64, i128, u32);
700
701// ─────────────────────────────────────────────────────────────────────────────
702// Operator implementations
703// ─────────────────────────────────────────────────────────────────────────────
704
705impl<U: Unit, S: Scalar> Add for Quantity<U, S> {
706    type Output = Self;
707    #[inline]
708    fn add(self, rhs: Self) -> Self {
709        Self::new(self.0 + rhs.0)
710    }
711}
712
713impl<U: Unit, S: Scalar> AddAssign for Quantity<U, S> {
714    #[inline]
715    fn add_assign(&mut self, rhs: Self) {
716        self.0 += rhs.0;
717    }
718}
719
720impl<U: Unit, S: Scalar> Sub for Quantity<U, S> {
721    type Output = Self;
722    #[inline]
723    fn sub(self, rhs: Self) -> Self {
724        Self::new(self.0 - rhs.0)
725    }
726}
727
728impl<U: Unit, S: Scalar> SubAssign for Quantity<U, S> {
729    #[inline]
730    fn sub_assign(&mut self, rhs: Self) {
731        self.0 -= rhs.0;
732    }
733}
734
735impl<U: Unit, S: Scalar> Mul<S> for Quantity<U, S> {
736    type Output = Self;
737    #[inline]
738    fn mul(self, rhs: S) -> Self {
739        Self::new(self.0 * rhs)
740    }
741}
742
743impl<U: Unit, S: Scalar> MulAssign<S> for Quantity<U, S> {
744    /// In-place scalar multiplication.
745    ///
746    /// ```rust
747    /// use qtty_core::length::Meters;
748    ///
749    /// let mut d = Meters::new(3.0);
750    /// d *= 4.0;
751    /// assert_eq!(d.value(), 12.0);
752    /// ```
753    ///
754    /// ```compile_fail
755    /// use qtty_core::length::Meters;
756    ///
757    /// let mut d = Meters::new(3.0);
758    /// d *= Meters::new(4.0);
759    /// ```
760    #[inline]
761    fn mul_assign(&mut self, rhs: S) {
762        self.0 *= rhs;
763    }
764}
765
766impl<U: Unit, S: Scalar> Div<S> for Quantity<U, S> {
767    type Output = Self;
768    #[inline]
769    fn div(self, rhs: S) -> Self {
770        Self::new(self.0 / rhs)
771    }
772}
773
774impl<U: Unit, S: Scalar> DivAssign<S> for Quantity<U, S> {
775    /// In-place scalar division.
776    ///
777    /// ```rust
778    /// use qtty_core::length::Meters;
779    ///
780    /// let mut d = Meters::new(120.0);
781    /// d /= 60.0;
782    /// assert_eq!(d.value(), 2.0);
783    /// ```
784    ///
785    /// ```compile_fail
786    /// use qtty_core::length::Meters;
787    ///
788    /// let mut d = Meters::new(120.0);
789    /// d /= Meters::new(60.0);
790    /// ```
791    #[inline]
792    fn div_assign(&mut self, rhs: S) {
793        self.0 /= rhs;
794    }
795}
796
797impl<U: Unit, S: Scalar + Neg<Output = S>> Neg for Quantity<U, S> {
798    type Output = Self;
799    #[inline]
800    fn neg(self) -> Self {
801        Self::new(-self.0)
802    }
803}
804
805// Multiplication of f64 * Quantity<U, f64>
806impl<U: Unit> Mul<Quantity<U, f64>> for f64 {
807    type Output = Quantity<U, f64>;
808    #[inline]
809    fn mul(self, rhs: Quantity<U, f64>) -> Self::Output {
810        rhs * self
811    }
812}
813
814// Multiplication of f32 * Quantity<U, f32>
815impl<U: Unit> Mul<Quantity<U, f32>> for f32 {
816    type Output = Quantity<U, f32>;
817    #[inline]
818    fn mul(self, rhs: Quantity<U, f32>) -> Self::Output {
819        rhs * self
820    }
821}
822
823// Multiplication for Rational64 (feature-gated)
824#[cfg(feature = "scalar-rational")]
825impl<U: Unit> Mul<Quantity<U, num_rational::Rational64>> for num_rational::Rational64 {
826    type Output = Quantity<U, num_rational::Rational64>;
827    #[inline]
828    fn mul(self, rhs: Quantity<U, num_rational::Rational64>) -> Self::Output {
829        rhs * self
830    }
831}
832
833// Multiplication for Rational32 (feature-gated)
834#[cfg(feature = "scalar-rational")]
835impl<U: Unit> Mul<Quantity<U, num_rational::Rational32>> for num_rational::Rational32 {
836    type Output = Quantity<U, num_rational::Rational32>;
837    #[inline]
838    fn mul(self, rhs: Quantity<U, num_rational::Rational32>) -> Self::Output {
839        rhs * self
840    }
841}
842
843// Commutative multiplication for signed integer scalars
844macro_rules! impl_int_commutative_mul {
845    ($($t:ty),*) => { $(
846        impl<U: Unit> Mul<Quantity<U, $t>> for $t {
847            type Output = Quantity<U, $t>;
848            #[inline]
849            fn mul(self, rhs: Quantity<U, $t>) -> Self::Output {
850                rhs * self
851            }
852        }
853    )* };
854}
855
856impl_int_commutative_mul!(i8, i16, i32, i64, i128, u32);
857
858// Rem for types that implement Rem (floats and integers)
859impl<U: Unit, S: Scalar + Rem<Output = S>> Rem<S> for Quantity<U, S> {
860    type Output = Self;
861    #[inline]
862    fn rem(self, rhs: S) -> Self {
863        Self::new(self.0 % rhs)
864    }
865}
866
867// Same-unit remainder: `5 m % 3 m == 2 m`.
868impl<U: Unit, S: Scalar + Rem<Output = S>> Rem for Quantity<U, S> {
869    type Output = Self;
870    #[inline]
871    fn rem(self, rhs: Self) -> Self {
872        Self::new(self.0 % rhs.0)
873    }
874}
875
876// PartialOrd between quantities of the same unit/scalar.
877impl<U: Unit, S: Scalar> PartialOrd for Quantity<U, S> {
878    #[inline]
879    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
880        self.0.partial_cmp(&other.0)
881    }
882}
883
884// Eq for scalar types that support total equality (integers, rationals, decimals)
885impl<U: Unit, S: Scalar + Eq> Eq for Quantity<U, S> {}
886
887// Hash for scalar types that support hashing, enabling Quantity in HashMap/HashSet.
888impl<U: Unit, S: Scalar + Hash> Hash for Quantity<U, S> {
889    #[inline]
890    fn hash<H: Hasher>(&self, state: &mut H) {
891        self.0.hash(state);
892    }
893}
894
895// Ord for scalar types that support total ordering (integers, rationals, decimals)
896impl<U: Unit, S: Scalar + Ord> Ord for Quantity<U, S> {
897    #[inline]
898    fn cmp(&self, other: &Self) -> Ordering {
899        self.0.cmp(&other.0)
900    }
901}
902
903// From scalar
904impl<U: Unit, S: Scalar> From<S> for Quantity<U, S> {
905    #[inline]
906    fn from(value: S) -> Self {
907        Self::new(value)
908    }
909}
910
911// Sum quantities into a quantity of the same unit/scalar.
912impl<U: Unit, S: Scalar> Sum for Quantity<U, S> {
913    #[inline]
914    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
915        iter.fold(Self::zero(), |acc, q| acc + q)
916    }
917}
918
919impl<'a, U: Unit, S: Scalar> Sum<&'a Quantity<U, S>> for Quantity<U, S> {
920    #[inline]
921    fn sum<I: Iterator<Item = &'a Quantity<U, S>>>(iter: I) -> Self {
922        iter.fold(Self::zero(), |acc, q| acc + *q)
923    }
924}
925
926// ─────────────────────────────────────────────────────────────────────────────
927// Division delegating to UnitDiv + QuantityDivOutput
928// ─────────────────────────────────────────────────────────────────────────────
929
930// When the two units are the same (`N = D`), `UnitDiv` returns `SameDivOutput`
931// and `QuantityDivOutput` maps that to `S` — the raw scalar.
932// For different units, `UnitDiv` returns a composite unit and `QuantityDivOutput`
933// wraps it in `Quantity<..., S>`.
934impl<N: Unit, D: Unit, S: Scalar> Div<Quantity<D, S>> for Quantity<N, S>
935where
936    N: UnitDiv<D>,
937    <N as UnitDiv<D>>::Output: QuantityDivOutput<S>,
938{
939    type Output = <<N as UnitDiv<D>>::Output as QuantityDivOutput<S>>::Output;
940    #[inline]
941    fn div(self, rhs: Quantity<D, S>) -> Self::Output {
942        <<N as UnitDiv<D>>::Output as QuantityDivOutput<S>>::wrap(self.0 / rhs.0)
943    }
944}
945
946// ─────────────────────────────────────────────────────────────────────────────
947// Multiplication delegating to UnitMul
948// ─────────────────────────────────────────────────────────────────────────────
949
950impl<A: Unit, B: Unit, S: Scalar> Mul<Quantity<B, S>> for Quantity<A, S>
951where
952    A: UnitMul<B>,
953{
954    type Output = Quantity<<A as UnitMul<B>>::Output, S>;
955
956    #[inline]
957    fn mul(self, rhs: Quantity<B, S>) -> Self::Output {
958        Quantity::new(self.0 * rhs.0)
959    }
960}
961
962// ─────────────────────────────────────────────────────────────────────────────
963// Trig helpers for dimensionless quantities
964// ─────────────────────────────────────────────────────────────────────────────
965
966// ─────────────────────────────────────────────────────────────────────────────
967// Dimensionally-typed square root
968// ─────────────────────────────────────────────────────────────────────────────
969
970impl<U, S> Quantity<U, S>
971where
972    U: UnitSqrt,
973    S: Real,
974{
975    /// Dimensionally-typed square root.
976    ///
977    /// For any squared unit `U = Prod<R, R>` (e.g. `SquareMeter ≡ Prod<Meter, Meter>`),
978    /// this returns a `Quantity<R, S>` whose value is the scalar square root.
979    ///
980    /// This is the inverse of the `Quantity<R> * Quantity<R> -> Quantity<Prod<R, R>>`
981    /// multiplication produced by [`UnitMul`].
982    ///
983    /// Only nonneg values make dimensional sense; for negative scalars the
984    /// returned quantity carries `S::sqrt(neg)` (typically NaN for real
985    /// scalars). Callers that need explicit signed-safety should branch on
986    /// [`signum`](Self::signum) first.
987    ///
988    /// # Example
989    ///
990    /// ```rust
991    /// use qtty_core::area::SquareMeters;
992    /// use qtty_core::length::{Meter, Meters};
993    /// use qtty_core::Quantity;
994    ///
995    /// let area = SquareMeters::new(25.0);
996    /// let side: Quantity<Meter> = area.sqrt();
997    /// assert!((side.value() - 5.0).abs() < 1e-12);
998    /// ```
999    #[inline]
1000    pub fn sqrt(self) -> Quantity<U::Root, S> {
1001        Quantity::new(self.0.sqrt())
1002    }
1003}
1004
1005impl<U, S> Quantity<U, S>
1006where
1007    U: Unit<Dim = crate::dimension::Dimensionless>,
1008    S: Transcendental,
1009{
1010    /// Arc sine returning a typed angle in radians.
1011    ///
1012    /// Applicable to any quantity whose dimension is `Dimensionless`, such as
1013    /// `Quantity<Per<Meter, Meter>, f64>` — the result of dividing different
1014    /// length units that share the same dimension.
1015    ///
1016    /// ```rust
1017    /// use qtty_core::angular::{Degree, Radian};
1018    /// use qtty_core::length::{Kilometer, Meter, Kilometers, Meters};
1019    /// use qtty_core::{Per, Quantity};
1020    ///
1021    /// // Cross-unit ratio: 1 km / 2000 m  =  0.5  (dimensionless)
1022    /// let km_per_m: Quantity<Per<Kilometer, Meter>> = Quantity::new(0.5);
1023    /// let angle: Quantity<Radian> = km_per_m.asin_angle();
1024    /// assert!((angle.value() - 0.5_f64.asin()).abs() < 1e-12);
1025    ///
1026    /// // Convert to degrees:
1027    /// let deg: Quantity<Degree> = angle.to();
1028    /// assert!((deg.value() - 30.0).abs() < 1e-10);
1029    /// ```
1030    #[inline]
1031    pub fn asin_angle(&self) -> Quantity<crate::units::angular::Radian, S> {
1032        Quantity::new(self.0.asin())
1033    }
1034
1035    /// Arc cosine returning a typed angle in radians.
1036    #[inline]
1037    pub fn acos_angle(&self) -> Quantity<crate::units::angular::Radian, S> {
1038        Quantity::new(self.0.acos())
1039    }
1040
1041    /// Arc tangent returning a typed angle in radians.
1042    #[inline]
1043    pub fn atan_angle(&self) -> Quantity<crate::units::angular::Radian, S> {
1044        Quantity::new(self.0.atan())
1045    }
1046}
1047
1048// ─────────────────────────────────────────────────────────────────────────────
1049// Transcendental helpers on dimensionless quantities (Real suffices)
1050// ─────────────────────────────────────────────────────────────────────────────
1051
1052impl<U, S> Quantity<U, S>
1053where
1054    U: Unit<Dim = crate::dimension::Dimensionless>,
1055    S: Real,
1056{
1057    /// Returns e^self as a typed dimensionless [`Ratio`](crate::units::dimensionless::Ratio).
1058    ///
1059    /// Useful for density-scale-height models, population factors, and any
1060    /// expression of the form `exp(-h / H)` where the argument is dimensionless.
1061    #[inline]
1062    pub fn exp(self) -> Quantity<crate::units::dimensionless::Ratio, S> {
1063        Quantity::new(self.0.exp())
1064    }
1065
1066    /// Returns the natural logarithm ln(self) as a typed dimensionless [`Ratio`].
1067    ///
1068    /// The result is dimensionless regardless of which dimensionless unit `U` is used.
1069    #[inline]
1070    pub fn ln(self) -> Quantity<crate::units::dimensionless::Ratio, S> {
1071        Quantity::new(self.0.ln())
1072    }
1073
1074    /// Returns self raised to the integer power `n` as a typed dimensionless [`Ratio`].
1075    ///
1076    /// Dimensionless^n is still dimensionless for any integer `n`.
1077    #[inline]
1078    pub fn powi(self, n: i32) -> Quantity<crate::units::dimensionless::Ratio, S> {
1079        Quantity::new(self.0.powi(n))
1080    }
1081
1082    /// Returns self raised to the dimensionless power `exp` as a typed dimensionless [`Ratio`].
1083    ///
1084    /// The exponent must itself be a dimensionless [`Ratio`] so the intent is explicit.
1085    #[inline]
1086    pub fn powf(
1087        self,
1088        exp: Quantity<crate::units::dimensionless::Ratio, S>,
1089    ) -> Quantity<crate::units::dimensionless::Ratio, S> {
1090        Quantity::new(self.0.powf(exp.0))
1091    }
1092}
1093
1094// ─────────────────────────────────────────────────────────────────────────────
1095// ratio_to: same-dimension division returning a typed Ratio
1096// ─────────────────────────────────────────────────────────────────────────────
1097
1098impl<U: Unit, S: Real> Quantity<U, S> {
1099    /// Divides `self` by `other` (same unit), returning a typed dimensionless
1100    /// [`Ratio`](crate::units::dimensionless::Ratio) instead of a raw scalar.
1101    ///
1102    /// This is an opt-in alternative to the standard `Quantity / Quantity`
1103    /// operator, which returns the raw scalar `S` for same-unit division.
1104    /// Use `ratio_to` whenever you want the result wrapped in a `Quantity`
1105    /// for downstream typed APIs.
1106    ///
1107    /// # Example
1108    ///
1109    /// ```rust
1110    /// use qtty_core::length::Meters;
1111    /// use qtty_core::units::dimensionless::Ratios;
1112    ///
1113    /// let a = Meters::new(10.0);
1114    /// let b = Meters::new(4.0);
1115    /// let r: Ratios = a.ratio_to(b);
1116    /// assert!((r.value() - 2.5).abs() < 1e-12);
1117    /// ```
1118    #[inline]
1119    pub fn ratio_to(self, other: Self) -> Quantity<crate::units::dimensionless::Ratio, S> {
1120        Quantity::new(self.0 / other.0)
1121    }
1122}