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}