Skip to main content

qtty_core/units/
unitless.rs

1//! Dimensionless helpers.
2//!
3//! This module contains small adapters for working with dimensionless values.
4//!
5//! The provided conversion from a dimensioned quantity to a unitless quantity is *lossy*: it drops the unit type
6//! without performing any normalization. The numeric value is preserved as-is.
7//!
8//! ```rust
9//! use qtty_core::time::Seconds;
10//! use qtty_core::{Quantity, Unitless};
11//!
12//! let t = Seconds::new(3.0);
13//! let u: Quantity<Unitless> = t.into();
14//! assert_eq!(u.value(), 3.0);
15//! ```
16
17use crate::dimension::{
18    Acceleration, AmountOfSubstance, Angular, Area, Current, Energy, Force, FrequencyDim, Length,
19    LuminousIntensity, Mass, Power, Temperature, Time, VelocityDim, Volume,
20};
21use crate::scalar::Scalar;
22use crate::{Quantity, Unit, Unitless};
23
24trait SupportedDimension {}
25impl SupportedDimension for Length {}
26impl SupportedDimension for Time {}
27impl SupportedDimension for Mass {}
28impl SupportedDimension for Temperature {}
29impl SupportedDimension for Current {}
30impl SupportedDimension for AmountOfSubstance {}
31impl SupportedDimension for LuminousIntensity {}
32impl SupportedDimension for Angular {}
33impl SupportedDimension for Area {}
34impl SupportedDimension for Volume {}
35impl SupportedDimension for VelocityDim {}
36impl SupportedDimension for Acceleration {}
37impl SupportedDimension for Force {}
38impl SupportedDimension for Energy {}
39impl SupportedDimension for Power {}
40impl SupportedDimension for FrequencyDim {}
41
42trait DimensionedUnit: Unit {}
43impl<U: Unit> DimensionedUnit for U where U::Dim: SupportedDimension {}
44
45impl<U: DimensionedUnit, S: Scalar> From<Quantity<U, S>> for Quantity<Unitless, S> {
46    #[inline]
47    fn from(quantity: Quantity<U, S>) -> Self {
48        Self::new(quantity.value())
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::units::angular::Degrees;
56    use crate::units::length::Meters;
57    use crate::units::mass::Kilogram;
58    use crate::units::mass::Kilograms;
59    use crate::units::time::Seconds;
60    use crate::Unit;
61    use approx::assert_abs_diff_eq;
62    use proptest::prelude::*;
63
64    // ─────────────────────────────────────────────────────────────────────────────
65    // Basic Unitless behavior
66    // ─────────────────────────────────────────────────────────────────────────────
67
68    #[test]
69    fn unitless_new_and_value() {
70        let u: Quantity<Unitless> = Quantity::new(42.0);
71        assert_eq!(u.value(), 42.0);
72    }
73
74    #[test]
75    fn unitless_from_f64() {
76        let u: Quantity<Unitless> = 1.23456.into();
77        assert_abs_diff_eq!(u.value(), 1.23456, epsilon = 1e-12);
78    }
79
80    // ─────────────────────────────────────────────────────────────────────────────
81    // Display formatting
82    // ─────────────────────────────────────────────────────────────────────────────
83
84    #[test]
85    fn display_unitless() {
86        let u: Quantity<Unitless> = Quantity::new(123.456);
87        let s = format!("{}", u);
88        assert_eq!(s, "123.456");
89    }
90
91    #[test]
92    fn display_unitless_integer() {
93        let u: Quantity<Unitless> = Quantity::new(42.0);
94        let s = format!("{}", u);
95        assert_eq!(s, "42");
96    }
97
98    // ─────────────────────────────────────────────────────────────────────────────
99    // Conversion from dimensioned quantities
100    // ─────────────────────────────────────────────────────────────────────────────
101
102    #[test]
103    fn from_length() {
104        let m = Meters::new(42.0);
105        let u: Quantity<Unitless> = m.into();
106        assert_eq!(u.value(), 42.0);
107    }
108
109    #[test]
110    fn from_time() {
111        let t = Seconds::new(5.0);
112        let u: Quantity<Unitless> = t.into();
113        assert_eq!(u.value(), 5.0);
114    }
115
116    #[test]
117    fn from_mass() {
118        let m = Kilograms::new(2.5);
119        let u: Quantity<Unitless> = m.into();
120        assert_eq!(u.value(), 2.5);
121    }
122
123    #[test]
124    fn from_angular() {
125        let a = Degrees::new(90.0);
126        let u: Quantity<Unitless> = a.into();
127        assert_eq!(u.value(), 90.0);
128    }
129
130    #[test]
131    fn from_mass_preserves_non_default_scalar_type() {
132        let m: Quantity<Kilogram, i32> = Quantity::new(7);
133        let u: Quantity<Unitless, i32> = m.into();
134        assert_eq!(u.value(), 7);
135    }
136
137    // ─────────────────────────────────────────────────────────────────────────────
138    // Arithmetic operations
139    // ─────────────────────────────────────────────────────────────────────────────
140
141    #[test]
142    fn unitless_addition() {
143        let a: Quantity<Unitless> = Quantity::new(3.0);
144        let b: Quantity<Unitless> = Quantity::new(4.0);
145        assert_eq!((a + b).value(), 7.0);
146    }
147
148    #[test]
149    fn unitless_subtraction() {
150        let a: Quantity<Unitless> = Quantity::new(10.0);
151        let b: Quantity<Unitless> = Quantity::new(3.0);
152        assert_eq!((a - b).value(), 7.0);
153    }
154
155    #[test]
156    fn unitless_multiplication() {
157        let a: Quantity<Unitless> = Quantity::new(3.0);
158        assert_eq!((a * 4.0).value(), 12.0);
159    }
160
161    #[test]
162    fn unitless_division() {
163        let a: Quantity<Unitless> = Quantity::new(12.0);
164        assert_eq!((a / 4.0).value(), 3.0);
165    }
166
167    // ─────────────────────────────────────────────────────────────────────────────
168    // Unit trait implementation
169    // ─────────────────────────────────────────────────────────────────────────────
170
171    #[test]
172    fn unitless_ratio() {
173        assert_eq!(Unitless::RATIO, 1.0);
174    }
175
176    #[test]
177    fn unitless_symbol() {
178        assert_eq!(Unitless::SYMBOL, "");
179    }
180
181    // ─────────────────────────────────────────────────────────────────────────────
182    // Property-based tests
183    // ─────────────────────────────────────────────────────────────────────────────
184
185    proptest! {
186        #[test]
187        fn prop_unitless_arithmetic(a in -1e6..1e6f64, b in -1e6..1e6f64) {
188            let qa: Quantity<Unitless> = Quantity::new(a);
189            let qb: Quantity<Unitless> = Quantity::new(b);
190
191            // Addition is commutative
192            prop_assert!((((qa + qb).value() - (qb + qa).value()).abs() < 1e-9));
193
194            // Value is preserved
195            prop_assert!(((qa + qb).value() - (a + b)).abs() < 1e-9);
196        }
197
198        #[test]
199        fn prop_from_length_preserves_value(v in -1e6..1e6f64) {
200            let m = Meters::new(v);
201            let u: Quantity<Unitless> = m.into();
202            prop_assert!((u.value() - v).abs() < 1e-12);
203        }
204
205        #[test]
206        fn prop_from_time_preserves_value(v in -1e6..1e6f64) {
207            let t = Seconds::new(v);
208            let u: Quantity<Unitless> = t.into();
209            prop_assert!((u.value() - v).abs() < 1e-12);
210        }
211    }
212}