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}