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