dv_rs/
lib.rs

1#![doc = include_str!("../DOCS.md")]
2
3pub mod units;
4
5pub struct DimensionalVariable {
6    pub value: f64,
7    pub unit: [f64; units::BASE_UNITS_SIZE],
8}
9
10/// A struct representing a dimensional variable with a value and a unit.
11impl DimensionalVariable {
12    /// Creates a new DimensionalVariable with the given value and unit.
13    ///
14    /// Rules for writing the unit string:
15    /// - Use a single `/` as a delimiter between the numerator and denominator.
16    /// - Use `-` as a delimiter between individual units
17    /// - Exponents can be represented either by using `^` to indicate exponent (ex. `m^2`) or without the delimiter (ex. `m2`)
18    /// - Inverses can be represented either by negative exponents or in the denominator (ex. `m^-2` or `1/m^2`)
19    /// 
20    /// Returns an error if the unit string is invalid or contains unknown units.
21    pub fn new(value: f64, unit_str: &str) -> Result<Self, String> {
22        // Fetch the unit details from the unit map
23        let (base_unit, conversion_factor) = unit_str_to_base_unit(unit_str)
24            .map_err(|e| format!("Failed to parse unit '{}': {}", unit_str, e))?;
25
26        // Create the DimensionalVariable with the converted value
27        return Ok(DimensionalVariable {
28            value: value * conversion_factor,
29            unit: base_unit,
30        });
31    }
32
33    /// Returns the value of this DimensionalVariable.
34    pub fn value(&self) -> f64 {
35        self.value
36    }
37
38    /// Converts the value of this DimensionalVariable to the specified unit.
39    /// Returns an error if the unit string is invalid or incompatible.
40    pub fn value_in(&self, unit_str: &str) -> Result<f64, String> {
41
42        let (unit, conversion_factor) = unit_str_to_base_unit(unit_str)
43            .map_err(|e| format!("Failed to parse unit '{}': {}", unit_str, e))?;
44
45        // Check if the units are compatible
46        if !self.check_compatibility(unit) {
47            return Err(format!("Incompatible unit conversion to: {}", unit_str));
48        }
49        
50        return Ok(self.value / conversion_factor);
51    }
52
53    pub fn check_compatibility(&self, other_unit: [f64; units::BASE_UNITS_SIZE]) -> bool {
54
55       // If the angle unit == 1, then allow compatibility to linear units
56       if self.unit[7] == 1.0 {
57            // Compare only the first 7 base units (ignore the angle unit)
58            return &self.unit[..units::BASE_UNITS_SIZE - 1] == &other_unit[..units::BASE_UNITS_SIZE - 1];
59       }
60
61        return self.unit == other_unit;
62    }
63
64    /// Returns the base unit array of this DimensionalVariable.
65    pub fn unit(&self) -> [f64; units::BASE_UNITS_SIZE] {
66        return self.unit;
67    }
68
69    /// Returns whether the variable is unitless (all base exponents are 0).
70    pub fn is_unitless(&self) -> bool {
71        return self.unit.iter().all(|&e| e == 0.0);
72    }
73
74    /// Fallible add with unit compatibility check.
75    pub fn try_add(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
76        if !self.check_compatibility(other.unit) {
77            return Err("Incompatible units for addition".to_string());
78        }
79        return Ok(DimensionalVariable { value: self.value + other.value, unit: self.unit });
80    }
81
82    /// Fallible subtraction with unit compatibility check.
83    pub fn try_sub(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
84        if !self.check_compatibility(other.unit) {
85            return Err("Incompatible units for subtraction".to_string());
86        }
87        return Ok(DimensionalVariable { value: self.value - other.value, unit: self.unit });
88    }
89
90    // ---- Math: powers and roots ----
91    /// Raise to integer power. Units exponents are multiplied by exp.
92    /// Returns a new DimensionalVariable.
93    pub fn powi(&self, exp: i32) -> DimensionalVariable {
94        let mut unit = self.unit;
95    for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp as f64; }
96        return DimensionalVariable { value: self.value.powi(exp), unit };
97    }
98
99    /// Raise to floating power.
100    /// Returns a new DimensionalVariable.
101    pub fn powf(&self, exp: f64) -> Result<DimensionalVariable, String> {
102        let mut unit = self.unit;
103        for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp; }
104        return Ok(DimensionalVariable { value: self.value.powf(exp), unit });
105    }
106
107    /// Square root. Allowed only when all unit exponents are value >= 0 (no complex results).
108    /// Returns a new DimensionalVariable.
109    pub fn sqrt(&self) -> Result<DimensionalVariable, String> {
110        if self.value < 0.0 {
111            return Err("sqrt of negative value".to_string());
112        }
113        return self.powf(0.5);
114    }
115
116    // ---- Math: logarithms (unitless only) ----
117    /// Natural logarithm. Requires unitless and value > 0.
118    pub fn ln(&self) -> Result<f64, String> {
119        if !self.is_unitless() { return Err("ln requires a unitless quantity".to_string()); }
120        if self.value <= 0.0 { return Err("ln domain error (value <= 0)".to_string()); }
121        Ok(self.value.ln())
122    }
123
124    /// Base-2 logarithm. Requires unitless and value > 0.
125    pub fn log2(&self) -> Result<f64, String> {
126        if !self.is_unitless() { return Err("log2 requires a unitless quantity".to_string()); }
127        if self.value <= 0.0 { return Err("log2 domain error (value <= 0)".to_string()); }
128        Ok(self.value.log2())
129    }
130
131    /// Base-10 logarithm. Requires unitless and value > 0.
132    pub fn log10(&self) -> Result<f64, String> {
133        if !self.is_unitless() { return Err("log10 requires a unitless quantity".to_string()); }
134        if self.value <= 0.0 { return Err("log10 domain error (value <= 0)".to_string()); }
135        Ok(self.value.log10())
136    }
137
138    // ---- Math: trigonometry (requires angle dimension or unitless) ----
139    /// Check if the variable has angle dimension (only rad exponent is non-zero)
140    fn is_angle(&self) -> bool {
141        // Angle dimension: [0, 0, 0, 0, 0, 0, 0, 1] (only rad exponent is 1)
142        for i in 0..units::BASE_UNITS_SIZE - 1 {
143            if self.unit[i] != 0.0 {
144                return false;
145            }
146        }
147        self.unit[units::BASE_UNITS_SIZE - 1] == 1.0
148    }
149
150    /// Sine function. Requires angle (radians).
151    pub fn sin(&self) -> Result<f64, String> {
152        if !self.is_angle() { 
153            return Err("sin requires an angle quantity".to_string()); 
154        }
155        Ok(self.value.sin())
156    }
157
158    /// Cosine function. Requires angle (radians).
159    pub fn cos(&self) -> Result<f64, String> {
160        if !self.is_angle() { 
161            return Err("cos requires an angle quantity".to_string()); 
162        }
163        Ok(self.value.cos())
164    }
165
166    /// Tangent function. Requires angle (radians).
167    pub fn tan(&self) -> Result<f64, String> {
168        if !self.is_angle() { 
169            return Err("tan requires an angle quantity".to_string()); 
170        }
171        Ok(self.value.tan())
172    }
173
174    /// Arcsine function. Requires unitless value in [-1, 1]. Returns angle in radians.
175    pub fn asin(&self) -> Result<DimensionalVariable, String> {
176        if !self.is_unitless() {
177            return Err("asin requires a unitless quantity".to_string());
178        }
179        if self.value < -1.0 || self.value > 1.0 {
180            return Err("asin requires a value in the range [-1, 1]".to_string());
181        }
182        let mut unit = [0.0; units::BASE_UNITS_SIZE];
183        unit[units::BASE_UNITS_SIZE - 1] = 1.0; // radians
184        Ok(DimensionalVariable { value: self.value.asin(), unit })
185    }
186
187    /// Arccosine function. Requires unitless value in [-1, 1]. Returns angle in radians.
188    pub fn acos(&self) -> Result<DimensionalVariable, String> {
189        if !self.is_unitless() {
190            return Err("acos requires a unitless quantity".to_string());
191        }
192        if self.value < -1.0 || self.value > 1.0 {
193            return Err("acos requires a value in the range [-1, 1]".to_string());
194        }
195        let mut unit = [0.0; units::BASE_UNITS_SIZE];
196        unit[units::BASE_UNITS_SIZE - 1] = 1.0; // radians
197        Ok(DimensionalVariable { value: self.value.acos(), unit })
198    }
199
200    /// Arctangent function. Requires unitless value. Returns angle in radians.
201    pub fn atan(&self) -> Result<DimensionalVariable, String> {
202        if !self.is_unitless() {
203            return Err("atan requires a unitless quantity".to_string());
204        }
205        let mut unit = [0.0; units::BASE_UNITS_SIZE];
206        unit[units::BASE_UNITS_SIZE - 1] = 1.0; // radians
207        Ok(DimensionalVariable { value: self.value.atan(), unit })
208    }
209
210    // ---- Scalar helpers on single values ----
211    /// Negate the value, keeping the same unit.
212    pub fn neg(&self) -> DimensionalVariable {
213        DimensionalVariable { value: -self.value, unit: self.unit }
214    }
215
216    /// Returns the absolute value, keeping the same unit.
217    pub fn abs(&self) -> DimensionalVariable {
218        DimensionalVariable { value: self.value.abs(), unit: self.unit }
219    }
220 
221}
222
223impl std::fmt::Display for DimensionalVariable {
224    /// Formats the DimensionalVariable as a string in the form "value unit".
225    /// 
226    /// The unit is displayed in fraction style (e.g., "m/s^2") with:
227    /// - Positive exponents in the numerator
228    /// - Negative exponents in the denominator (as positive values)
229    /// - Exponents of 1 are omitted
230    /// - Units with exponent 0 are omitted
231    /// - Unitless quantities show "(unitless)"
232    /// 
233    /// Examples:
234    /// - 9.81 m/s^2 for acceleration
235    /// - 100 kg*m^2/s^2 for energy
236    /// - 3.14 rad for angle
237    /// - 5 (unitless) for dimensionless quantities
238    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239        let mut numerator_parts: Vec<String> = Vec::new();
240        let mut denominator_parts: Vec<String> = Vec::new();
241
242        for (i, &exp) in self.unit.iter().enumerate() {
243            if exp == 0.0 {
244                continue;
245            }
246
247            let unit_symbol = units::BASE_UNITS[i];
248
249            if exp > 0.0 {
250                if exp == 1.0 {
251                    numerator_parts.push(unit_symbol.to_string());
252                } else if exp == exp.trunc() {
253                    // Integer exponent
254                    numerator_parts.push(format!("{}^{}", unit_symbol, exp as i32));
255                } else {
256                    // Fractional exponent
257                    numerator_parts.push(format!("{}^{}", unit_symbol, exp));
258                }
259            } else {
260                let abs_exp = exp.abs();
261                if abs_exp == 1.0 {
262                    denominator_parts.push(unit_symbol.to_string());
263                } else if abs_exp == abs_exp.trunc() {
264                    // Integer exponent
265                    denominator_parts.push(format!("{}^{}", unit_symbol, abs_exp as i32));
266                } else {
267                    // Fractional exponent
268                    denominator_parts.push(format!("{}^{}", unit_symbol, abs_exp));
269                }
270            }
271        }
272
273        let unit_str = if numerator_parts.is_empty() && denominator_parts.is_empty() {
274            "(unitless)".to_string()
275        } else if denominator_parts.is_empty() {
276            numerator_parts.join("*")
277        } else if numerator_parts.is_empty() {
278            format!("1/{}", denominator_parts.join("*"))
279        } else {
280            format!("{}/{}", numerator_parts.join("*"), denominator_parts.join("*"))
281        };
282
283        write!(f, "{} {}", self.value, unit_str)
284    }
285}
286
287/// Arcsin function for f64 input, returns DimensionalVariable in radians.
288pub fn asin(x: f64) -> Result<DimensionalVariable, String> {
289    return DimensionalVariable::new(x, "").unwrap().asin();
290}
291
292/// Arccos function for f64 input, returns DimensionalVariable in radians.
293pub fn acos(x: f64) -> Result<DimensionalVariable, String> {
294    return DimensionalVariable::new(x, "").unwrap().acos();
295}
296
297/// Arctan function for f64 input, returns DimensionalVariable in radians.
298pub fn atan(x: f64) -> Result<DimensionalVariable, String> {
299    return DimensionalVariable::new(x, "").unwrap().atan();
300}
301
302/// Convert a unit string like "m/s^2" or "kg-m/s^2" into base unit exponents and a conversion factor.
303/// Returns an error if the unit string is invalid or contains unknown units.
304fn unit_str_to_base_unit(units_str: &str) -> Result<([f64; units::BASE_UNITS_SIZE], f64), String> {
305
306    // Start by removing any parentheses or brackets
307    let cleaned_units_str = units_str.replace(['(', ')', '[', ']'], "");
308
309    // Split the cleaned string by '/' to separate numerator and denominator
310    let parts: Vec<&str> = cleaned_units_str.split('/').collect();
311    if parts.len() > 2 {
312        return Err("Unit string can only have one '/'".to_string());
313    }
314
315    let mut base_unit =  [0.0; units::BASE_UNITS_SIZE];
316    let mut conversion_factor: f64 = 1.0;
317
318    for i in 0..parts.len() {
319        // Detect whether it's the numerator or denominator
320        let denominator_multiplier = if i == 1 { -1 } else { 1 };
321
322        // Split by '-' to handle individual units, but keep '-' that is an exponent sign (after '^')
323        let units: Vec<&str> = {
324            let s = parts[i];
325            let mut out = Vec::new();
326            let mut start = 0usize;
327            let mut prev: Option<char> = None;
328            for (idx, ch) in s.char_indices() {
329                if ch == '-' && prev != Some('^') {
330                    if idx > start {
331                        out.push(&s[start..idx]);
332                    }
333                    start = idx + ch.len_utf8();
334                }
335                prev = Some(ch);
336            }
337            if start < s.len() {
338                out.push(&s[start..]);
339            }
340            out
341        };
342        for unit_str in units {
343
344            let (base, power) = read_unit_power(unit_str)?;
345
346            let unit_map = units::unit_map();
347            let unit = unit_map.get(base)
348                .ok_or_else(|| format!("Unknown unit: {}", base))?; 
349
350            for j in 0..units::BASE_UNITS_SIZE {
351                base_unit[j] += unit.base_unit[j] * (power * denominator_multiplier) as f64;
352            }
353
354            // Apply the conversion factor
355            conversion_factor *= unit.conversion_factor.powi(power * denominator_multiplier);
356        }
357    }
358
359    return Ok((base_unit, conversion_factor));
360}
361
362/// Parse a token like "m3", "m-2", or "m^3"/"m^-2" into (base, power).
363/// If no trailing exponent is found, defaults to power = 1.
364/// Returns an error if a trailing '^' has no number.
365fn read_unit_power(unit: &str) -> Result<(&str, i32), String> {
366    let u = unit.trim();
367    if u.is_empty() {
368        return Err("Empty unit token".to_string());
369    }
370
371    let bytes = u.as_bytes();
372
373    // Find the trailing digits
374    let mut end = u.len();
375    while end > 0 && bytes[end - 1].is_ascii_digit() {
376        end -= 1;
377    }
378
379    if end == u.len() {
380        // No trailing digits. If it ends with '^', that's an error; otherwise power = 1.
381        if end > 0 && bytes[end - 1] == b'^' {
382            return Err(format!("Missing exponent after '^' in \"{}\"", u));
383        }
384        return Ok((u, 1));
385    }
386
387    let mut start = end;
388    if start > 0 && (bytes[start - 1] == b'-') {
389        start -= 1;
390    }
391
392    let exp_str = &u[start..];
393    let exp: i32 = exp_str
394        .parse()
395        .map_err(|_| format!("Unable to read numeric power from \"{}\"", u))?;
396
397    // Base is everything before the exponent; strip a trailing '^' if present.
398    let mut base_end = start;
399    if base_end > 0 && bytes[base_end - 1] == b'^' {
400        base_end -= 1;
401    }
402    let base = u[..base_end].trim();
403    if base.is_empty() {
404        return Err(format!("Missing unit symbol before exponent in \"{}\"", u));
405    }
406
407    Ok((base, exp))
408}
409
410// ---- Helpers for unit arithmetic ----
411/// Add two unit exponent arrays element-wise.
412fn add_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
413    let mut out = a;
414    for i in 0..units::BASE_UNITS_SIZE { out[i] += b[i]; }
415    out
416}
417
418/// Subtract two unit exponent arrays element-wise.
419fn sub_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
420    let mut out = a;
421    for i in 0..units::BASE_UNITS_SIZE { out[i] -= b[i]; }
422    out
423}
424
425// ---- Operator trait impls ----
426use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign};
427use std::cmp::Ordering;
428
429// Keep only reference-based binary ops to avoid duplication. Autoref handles owned values.
430impl<'a, 'b> Add<&'b DimensionalVariable> for &'a DimensionalVariable {
431    type Output = DimensionalVariable;
432    fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
433        return self.try_add(rhs).expect("Incompatible units for addition");
434    }
435}
436
437// Delegating wrappers for owned LHS/RHS
438impl Add<DimensionalVariable> for DimensionalVariable {
439    type Output = DimensionalVariable;
440    fn add(self, rhs: DimensionalVariable) -> Self::Output {
441        <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, &rhs)
442    }
443}
444
445impl<'b> Add<&'b DimensionalVariable> for DimensionalVariable {
446    type Output = DimensionalVariable;
447    fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
448        <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, rhs)
449    }
450}
451
452impl<'a> Add<DimensionalVariable> for &'a DimensionalVariable {
453    type Output = DimensionalVariable;
454    fn add(self, rhs: DimensionalVariable) -> Self::Output {
455        <&DimensionalVariable as Add<&DimensionalVariable>>::add(self, &rhs)
456    }
457}
458
459impl<'a, 'b> Sub<&'b DimensionalVariable> for &'a DimensionalVariable {
460    type Output = DimensionalVariable;
461    fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
462        return self.try_sub(rhs).expect("Incompatible units for subtraction");
463    }
464}
465
466impl Sub<DimensionalVariable> for DimensionalVariable {
467    type Output = DimensionalVariable;
468    fn sub(self, rhs: DimensionalVariable) -> Self::Output {
469        <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, &rhs)
470    }
471}
472
473impl<'b> Sub<&'b DimensionalVariable> for DimensionalVariable {
474    type Output = DimensionalVariable;
475    fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
476        <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, rhs)
477    }
478}
479
480impl<'a> Sub<DimensionalVariable> for &'a DimensionalVariable {
481    type Output = DimensionalVariable;
482    fn sub(self, rhs: DimensionalVariable) -> Self::Output {
483        <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(self, &rhs)
484    }
485}
486
487impl<'a, 'b> Mul<&'b DimensionalVariable> for &'a DimensionalVariable {
488    type Output = DimensionalVariable;
489    fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
490        DimensionalVariable { value: self.value * rhs.value, unit: add_unit_exponents(self.unit, rhs.unit) }
491    }
492}
493
494impl Mul<DimensionalVariable> for DimensionalVariable {
495    type Output = DimensionalVariable;
496    fn mul(self, rhs: DimensionalVariable) -> Self::Output {
497        <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, &rhs)
498    }
499}
500
501impl<'b> Mul<&'b DimensionalVariable> for DimensionalVariable {
502    type Output = DimensionalVariable;
503    fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
504        <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, rhs)
505    }
506}
507
508impl<'a> Mul<DimensionalVariable> for &'a DimensionalVariable {
509    type Output = DimensionalVariable;
510    fn mul(self, rhs: DimensionalVariable) -> Self::Output {
511        <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(self, &rhs)
512    }
513}
514
515impl<'a, 'b> Div<&'b DimensionalVariable> for &'a DimensionalVariable {
516    type Output = DimensionalVariable;
517    fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
518        DimensionalVariable { value: self.value / rhs.value, unit: sub_unit_exponents(self.unit, rhs.unit) }
519    }
520}
521
522impl Div<DimensionalVariable> for DimensionalVariable {
523    type Output = DimensionalVariable;
524    fn div(self, rhs: DimensionalVariable) -> Self::Output {
525        <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, &rhs)
526    }
527}
528
529impl<'b> Div<&'b DimensionalVariable> for DimensionalVariable {
530    type Output = DimensionalVariable;
531    fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
532        <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, rhs)
533    }
534}
535
536impl<'a> Div<DimensionalVariable> for &'a DimensionalVariable {
537    type Output = DimensionalVariable;
538    fn div(self, rhs: DimensionalVariable) -> Self::Output {
539        <&DimensionalVariable as Div<&DimensionalVariable>>::div(self, &rhs)
540    }
541}
542
543// Assignment ops: implement only for &DimensionalVariable RHS. Owned RHS will autoref.
544impl AddAssign<&DimensionalVariable> for DimensionalVariable {
545    fn add_assign(&mut self, rhs: &DimensionalVariable) {
546        *self = self.try_add(rhs).expect("Incompatible units for addition assignment");
547    }
548}
549
550impl SubAssign<&DimensionalVariable> for DimensionalVariable {
551    fn sub_assign(&mut self, rhs: &DimensionalVariable) {
552        *self = self.try_sub(rhs).expect("Incompatible units for subtraction assignment");
553    }
554}
555
556impl MulAssign<&DimensionalVariable> for DimensionalVariable {
557    fn mul_assign(&mut self, rhs: &DimensionalVariable) {
558        self.value *= rhs.value;
559        self.unit = add_unit_exponents(self.unit, rhs.unit);
560    }
561}
562
563impl DivAssign<&DimensionalVariable> for DimensionalVariable {
564    fn div_assign(&mut self, rhs: &DimensionalVariable) {
565        self.value /= rhs.value;
566        self.unit = sub_unit_exponents(self.unit, rhs.unit);
567    }
568}
569
570// Scalar ops
571impl<'a> Mul<f64> for &'a DimensionalVariable {
572    type Output = DimensionalVariable;
573    fn mul(self, rhs: f64) -> Self::Output {
574        DimensionalVariable { value: self.value * rhs, unit: self.unit }
575    }
576}
577
578impl Mul<f64> for DimensionalVariable {
579    type Output = DimensionalVariable;
580    fn mul(self, rhs: f64) -> Self::Output {
581        <&DimensionalVariable as Mul<f64>>::mul(&self, rhs)
582    }
583}
584
585impl MulAssign<f64> for DimensionalVariable {
586    fn mul_assign(&mut self, rhs: f64) {
587        self.value *= rhs;
588    }
589}
590
591impl<'a> Div<f64> for &'a DimensionalVariable {
592    type Output = DimensionalVariable;
593    fn div(self, rhs: f64) -> Self::Output {
594        DimensionalVariable { value: self.value / rhs, unit: self.unit }
595    }
596}
597
598impl Div<f64> for DimensionalVariable {
599    type Output = DimensionalVariable;
600    fn div(self, rhs: f64) -> Self::Output {
601        <&DimensionalVariable as Div<f64>>::div(&self, rhs)
602    }
603}
604
605impl DivAssign<f64> for DimensionalVariable {
606    fn div_assign(&mut self, rhs: f64) {
607        self.value /= rhs;
608    }
609}
610
611// Symmetric scalar ops
612impl<'a> Mul<&'a DimensionalVariable> for f64 {
613    type Output = DimensionalVariable;
614    fn mul(self, rhs: &'a DimensionalVariable) -> Self::Output {
615        DimensionalVariable { value: self * rhs.value, unit: rhs.unit }
616    }
617}
618
619impl Mul<DimensionalVariable> for f64 {
620    type Output = DimensionalVariable;
621    fn mul(self, rhs: DimensionalVariable) -> Self::Output {
622        <f64 as Mul<&DimensionalVariable>>::mul(self, &rhs)
623    }
624}
625
626impl<'a> Div<&'a DimensionalVariable> for f64 {
627    type Output = DimensionalVariable;
628    fn div(self, rhs: &'a DimensionalVariable) -> Self::Output {
629    DimensionalVariable { value: self / rhs.value, unit: sub_unit_exponents([0.0; units::BASE_UNITS_SIZE], rhs.unit) }
630    }
631}
632
633impl Div<DimensionalVariable> for f64 {
634    type Output = DimensionalVariable;
635    fn div(self, rhs: DimensionalVariable) -> Self::Output {
636        <f64 as Div<&DimensionalVariable>>::div(self, &rhs)
637    }
638}
639
640// Unary negation on references and delegating owned variant
641impl<'a> Neg for &'a DimensionalVariable {
642    type Output = DimensionalVariable;
643    fn neg(self) -> Self::Output {
644        DimensionalVariable { value: -self.value, unit: self.unit }
645    }
646}
647
648impl Neg for DimensionalVariable {
649    type Output = DimensionalVariable;
650    fn neg(self) -> Self::Output {
651        <&DimensionalVariable as Neg>::neg(&self)
652    }
653}
654
655// ---- Comparisons: equalities and ordering ----
656impl PartialEq for DimensionalVariable {
657    fn eq(&self, other: &Self) -> bool {
658        if !self.check_compatibility(other.unit) {
659            return false;
660        }
661        self.value == other.value
662    }
663}
664
665impl PartialOrd for DimensionalVariable {
666    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
667        if !self.check_compatibility(other.unit) { return None; }
668        self.value.partial_cmp(&other.value)
669    }
670}
671
672#[cfg(test)]
673mod tests {
674    use super::read_unit_power;
675
676    #[test]
677    fn read_unit_power_basic_cases() {
678        assert_eq!(read_unit_power("m").unwrap(), ("m", 1));
679        assert_eq!(read_unit_power("m3").unwrap(), ("m", 3));
680        assert_eq!(read_unit_power("m^3").unwrap(), ("m", 3));
681        assert_eq!(read_unit_power("m^-2").unwrap(), ("m", -2));
682        assert_eq!(read_unit_power("m-2").unwrap(), ("m", -2));
683        assert_eq!(read_unit_power("  kg^2 ").unwrap(), ("kg", 2));
684        assert_eq!(read_unit_power("undef").unwrap(), ("undef", 1));    // We don't check for known units here
685    }
686
687    #[test]
688    fn read_unit_power_errors() {
689        assert!(read_unit_power("").is_err());
690        let err = read_unit_power("m^").unwrap_err();
691        assert!(err.contains("Missing exponent"));
692        let err = read_unit_power("^2").unwrap_err();
693        assert!(err.contains("Missing unit symbol"));
694    }
695}