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
223/// Arcsin function for f64 input, returns DimensionalVariable in radians.
224pub fn asin(x: f64) -> Result<DimensionalVariable, String> {
225    return DimensionalVariable::new(x, "").unwrap().asin();
226}
227
228/// Arccos function for f64 input, returns DimensionalVariable in radians.
229pub fn acos(x: f64) -> Result<DimensionalVariable, String> {
230    return DimensionalVariable::new(x, "").unwrap().acos();
231}
232
233/// Arctan function for f64 input, returns DimensionalVariable in radians.
234pub fn atan(x: f64) -> Result<DimensionalVariable, String> {
235    return DimensionalVariable::new(x, "").unwrap().atan();
236}
237
238/// Convert a unit string like "m/s^2" or "kg-m/s^2" into base unit exponents and a conversion factor.
239/// Returns an error if the unit string is invalid or contains unknown units.
240fn unit_str_to_base_unit(units_str: &str) -> Result<([f64; units::BASE_UNITS_SIZE], f64), String> {
241
242    // Start by removing any parentheses or brackets
243    let cleaned_units_str = units_str.replace(['(', ')', '[', ']'], "");
244
245    // Split the cleaned string by '/' to separate numerator and denominator
246    let parts: Vec<&str> = cleaned_units_str.split('/').collect();
247    if parts.len() > 2 {
248        return Err("Unit string can only have one '/'".to_string());
249    }
250
251    let mut base_unit =  [0.0; units::BASE_UNITS_SIZE];
252    let mut conversion_factor: f64 = 1.0;
253
254    for i in 0..parts.len() {
255        // Detect whether it's the numerator or denominator
256        let denominator_multiplier = if i == 1 { -1 } else { 1 };
257
258        // Split by '-' to handle individual units, but keep '-' that is an exponent sign (after '^')
259        let units: Vec<&str> = {
260            let s = parts[i];
261            let mut out = Vec::new();
262            let mut start = 0usize;
263            let mut prev: Option<char> = None;
264            for (idx, ch) in s.char_indices() {
265                if ch == '-' && prev != Some('^') {
266                    if idx > start {
267                        out.push(&s[start..idx]);
268                    }
269                    start = idx + ch.len_utf8();
270                }
271                prev = Some(ch);
272            }
273            if start < s.len() {
274                out.push(&s[start..]);
275            }
276            out
277        };
278        for unit_str in units {
279
280            let (base, power) = read_unit_power(unit_str)?;
281
282            let unit_map = units::unit_map();
283            let unit = unit_map.get(base)
284                .ok_or_else(|| format!("Unknown unit: {}", base))?; 
285
286            for j in 0..units::BASE_UNITS_SIZE {
287                base_unit[j] += unit.base_unit[j] * (power * denominator_multiplier) as f64;
288            }
289
290            // Apply the conversion factor
291            conversion_factor *= unit.conversion_factor.powi(power * denominator_multiplier);
292        }
293    }
294
295    return Ok((base_unit, conversion_factor));
296}
297
298/// Parse a token like "m3", "m-2", or "m^3"/"m^-2" into (base, power).
299/// If no trailing exponent is found, defaults to power = 1.
300/// Returns an error if a trailing '^' has no number.
301fn read_unit_power(unit: &str) -> Result<(&str, i32), String> {
302    let u = unit.trim();
303    if u.is_empty() {
304        return Err("Empty unit token".to_string());
305    }
306
307    let bytes = u.as_bytes();
308
309    // Find the trailing digits
310    let mut end = u.len();
311    while end > 0 && bytes[end - 1].is_ascii_digit() {
312        end -= 1;
313    }
314
315    if end == u.len() {
316        // No trailing digits. If it ends with '^', that's an error; otherwise power = 1.
317        if end > 0 && bytes[end - 1] == b'^' {
318            return Err(format!("Missing exponent after '^' in \"{}\"", u));
319        }
320        return Ok((u, 1));
321    }
322
323    let mut start = end;
324    if start > 0 && (bytes[start - 1] == b'-') {
325        start -= 1;
326    }
327
328    let exp_str = &u[start..];
329    let exp: i32 = exp_str
330        .parse()
331        .map_err(|_| format!("Unable to read numeric power from \"{}\"", u))?;
332
333    // Base is everything before the exponent; strip a trailing '^' if present.
334    let mut base_end = start;
335    if base_end > 0 && bytes[base_end - 1] == b'^' {
336        base_end -= 1;
337    }
338    let base = u[..base_end].trim();
339    if base.is_empty() {
340        return Err(format!("Missing unit symbol before exponent in \"{}\"", u));
341    }
342
343    Ok((base, exp))
344}
345
346// ---- Helpers for unit arithmetic ----
347/// Add two unit exponent arrays element-wise.
348fn add_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
349    let mut out = a;
350    for i in 0..units::BASE_UNITS_SIZE { out[i] += b[i]; }
351    out
352}
353
354/// Subtract two unit exponent arrays element-wise.
355fn sub_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
356    let mut out = a;
357    for i in 0..units::BASE_UNITS_SIZE { out[i] -= b[i]; }
358    out
359}
360
361// ---- Operator trait impls ----
362use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign};
363use std::cmp::Ordering;
364
365// Keep only reference-based binary ops to avoid duplication. Autoref handles owned values.
366impl<'a, 'b> Add<&'b DimensionalVariable> for &'a DimensionalVariable {
367    type Output = DimensionalVariable;
368    fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
369        return self.try_add(rhs).expect("Incompatible units for addition");
370    }
371}
372
373// Delegating wrappers for owned LHS/RHS
374impl Add<DimensionalVariable> for DimensionalVariable {
375    type Output = DimensionalVariable;
376    fn add(self, rhs: DimensionalVariable) -> Self::Output {
377        <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, &rhs)
378    }
379}
380
381impl<'b> Add<&'b DimensionalVariable> for DimensionalVariable {
382    type Output = DimensionalVariable;
383    fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
384        <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, rhs)
385    }
386}
387
388impl<'a> Add<DimensionalVariable> for &'a DimensionalVariable {
389    type Output = DimensionalVariable;
390    fn add(self, rhs: DimensionalVariable) -> Self::Output {
391        <&DimensionalVariable as Add<&DimensionalVariable>>::add(self, &rhs)
392    }
393}
394
395impl<'a, 'b> Sub<&'b DimensionalVariable> for &'a DimensionalVariable {
396    type Output = DimensionalVariable;
397    fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
398        return self.try_sub(rhs).expect("Incompatible units for subtraction");
399    }
400}
401
402impl Sub<DimensionalVariable> for DimensionalVariable {
403    type Output = DimensionalVariable;
404    fn sub(self, rhs: DimensionalVariable) -> Self::Output {
405        <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, &rhs)
406    }
407}
408
409impl<'b> Sub<&'b DimensionalVariable> for DimensionalVariable {
410    type Output = DimensionalVariable;
411    fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
412        <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, rhs)
413    }
414}
415
416impl<'a> Sub<DimensionalVariable> for &'a DimensionalVariable {
417    type Output = DimensionalVariable;
418    fn sub(self, rhs: DimensionalVariable) -> Self::Output {
419        <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(self, &rhs)
420    }
421}
422
423impl<'a, 'b> Mul<&'b DimensionalVariable> for &'a DimensionalVariable {
424    type Output = DimensionalVariable;
425    fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
426        DimensionalVariable { value: self.value * rhs.value, unit: add_unit_exponents(self.unit, rhs.unit) }
427    }
428}
429
430impl Mul<DimensionalVariable> for DimensionalVariable {
431    type Output = DimensionalVariable;
432    fn mul(self, rhs: DimensionalVariable) -> Self::Output {
433        <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, &rhs)
434    }
435}
436
437impl<'b> Mul<&'b DimensionalVariable> for DimensionalVariable {
438    type Output = DimensionalVariable;
439    fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
440        <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, rhs)
441    }
442}
443
444impl<'a> Mul<DimensionalVariable> for &'a DimensionalVariable {
445    type Output = DimensionalVariable;
446    fn mul(self, rhs: DimensionalVariable) -> Self::Output {
447        <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(self, &rhs)
448    }
449}
450
451impl<'a, 'b> Div<&'b DimensionalVariable> for &'a DimensionalVariable {
452    type Output = DimensionalVariable;
453    fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
454        DimensionalVariable { value: self.value / rhs.value, unit: sub_unit_exponents(self.unit, rhs.unit) }
455    }
456}
457
458impl Div<DimensionalVariable> for DimensionalVariable {
459    type Output = DimensionalVariable;
460    fn div(self, rhs: DimensionalVariable) -> Self::Output {
461        <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, &rhs)
462    }
463}
464
465impl<'b> Div<&'b DimensionalVariable> for DimensionalVariable {
466    type Output = DimensionalVariable;
467    fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
468        <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, rhs)
469    }
470}
471
472impl<'a> Div<DimensionalVariable> for &'a DimensionalVariable {
473    type Output = DimensionalVariable;
474    fn div(self, rhs: DimensionalVariable) -> Self::Output {
475        <&DimensionalVariable as Div<&DimensionalVariable>>::div(self, &rhs)
476    }
477}
478
479// Assignment ops: implement only for &DimensionalVariable RHS. Owned RHS will autoref.
480impl AddAssign<&DimensionalVariable> for DimensionalVariable {
481    fn add_assign(&mut self, rhs: &DimensionalVariable) {
482        *self = self.try_add(rhs).expect("Incompatible units for addition assignment");
483    }
484}
485
486impl SubAssign<&DimensionalVariable> for DimensionalVariable {
487    fn sub_assign(&mut self, rhs: &DimensionalVariable) {
488        *self = self.try_sub(rhs).expect("Incompatible units for subtraction assignment");
489    }
490}
491
492impl MulAssign<&DimensionalVariable> for DimensionalVariable {
493    fn mul_assign(&mut self, rhs: &DimensionalVariable) {
494        self.value *= rhs.value;
495        self.unit = add_unit_exponents(self.unit, rhs.unit);
496    }
497}
498
499impl DivAssign<&DimensionalVariable> for DimensionalVariable {
500    fn div_assign(&mut self, rhs: &DimensionalVariable) {
501        self.value /= rhs.value;
502        self.unit = sub_unit_exponents(self.unit, rhs.unit);
503    }
504}
505
506// Scalar ops
507impl<'a> Mul<f64> for &'a DimensionalVariable {
508    type Output = DimensionalVariable;
509    fn mul(self, rhs: f64) -> Self::Output {
510        DimensionalVariable { value: self.value * rhs, unit: self.unit }
511    }
512}
513
514impl Mul<f64> for DimensionalVariable {
515    type Output = DimensionalVariable;
516    fn mul(self, rhs: f64) -> Self::Output {
517        <&DimensionalVariable as Mul<f64>>::mul(&self, rhs)
518    }
519}
520
521impl MulAssign<f64> for DimensionalVariable {
522    fn mul_assign(&mut self, rhs: f64) {
523        self.value *= rhs;
524    }
525}
526
527impl<'a> Div<f64> for &'a DimensionalVariable {
528    type Output = DimensionalVariable;
529    fn div(self, rhs: f64) -> Self::Output {
530        DimensionalVariable { value: self.value / rhs, unit: self.unit }
531    }
532}
533
534impl Div<f64> for DimensionalVariable {
535    type Output = DimensionalVariable;
536    fn div(self, rhs: f64) -> Self::Output {
537        <&DimensionalVariable as Div<f64>>::div(&self, rhs)
538    }
539}
540
541impl DivAssign<f64> for DimensionalVariable {
542    fn div_assign(&mut self, rhs: f64) {
543        self.value /= rhs;
544    }
545}
546
547// Symmetric scalar ops
548impl<'a> Mul<&'a DimensionalVariable> for f64 {
549    type Output = DimensionalVariable;
550    fn mul(self, rhs: &'a DimensionalVariable) -> Self::Output {
551        DimensionalVariable { value: self * rhs.value, unit: rhs.unit }
552    }
553}
554
555impl Mul<DimensionalVariable> for f64 {
556    type Output = DimensionalVariable;
557    fn mul(self, rhs: DimensionalVariable) -> Self::Output {
558        <f64 as Mul<&DimensionalVariable>>::mul(self, &rhs)
559    }
560}
561
562impl<'a> Div<&'a DimensionalVariable> for f64 {
563    type Output = DimensionalVariable;
564    fn div(self, rhs: &'a DimensionalVariable) -> Self::Output {
565    DimensionalVariable { value: self / rhs.value, unit: sub_unit_exponents([0.0; units::BASE_UNITS_SIZE], rhs.unit) }
566    }
567}
568
569impl Div<DimensionalVariable> for f64 {
570    type Output = DimensionalVariable;
571    fn div(self, rhs: DimensionalVariable) -> Self::Output {
572        <f64 as Div<&DimensionalVariable>>::div(self, &rhs)
573    }
574}
575
576// Unary negation on references and delegating owned variant
577impl<'a> Neg for &'a DimensionalVariable {
578    type Output = DimensionalVariable;
579    fn neg(self) -> Self::Output {
580        DimensionalVariable { value: -self.value, unit: self.unit }
581    }
582}
583
584impl Neg for DimensionalVariable {
585    type Output = DimensionalVariable;
586    fn neg(self) -> Self::Output {
587        <&DimensionalVariable as Neg>::neg(&self)
588    }
589}
590
591// ---- Comparisons: equalities and ordering ----
592impl PartialEq for DimensionalVariable {
593    fn eq(&self, other: &Self) -> bool {
594        if !self.check_compatibility(other.unit) {
595            return false;
596        }
597        self.value == other.value
598    }
599}
600
601impl PartialOrd for DimensionalVariable {
602    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
603        if !self.check_compatibility(other.unit) { return None; }
604        self.value.partial_cmp(&other.value)
605    }
606}
607
608#[cfg(test)]
609mod tests {
610    use super::read_unit_power;
611
612    #[test]
613    fn read_unit_power_basic_cases() {
614        assert_eq!(read_unit_power("m").unwrap(), ("m", 1));
615        assert_eq!(read_unit_power("m3").unwrap(), ("m", 3));
616        assert_eq!(read_unit_power("m^3").unwrap(), ("m", 3));
617        assert_eq!(read_unit_power("m^-2").unwrap(), ("m", -2));
618        assert_eq!(read_unit_power("m-2").unwrap(), ("m", -2));
619        assert_eq!(read_unit_power("  kg^2 ").unwrap(), ("kg", 2));
620        assert_eq!(read_unit_power("undef").unwrap(), ("undef", 1));    // We don't check for known units here
621    }
622
623    #[test]
624    fn read_unit_power_errors() {
625        assert!(read_unit_power("").is_err());
626        let err = read_unit_power("m^").unwrap_err();
627        assert!(err.contains("Missing exponent"));
628        let err = read_unit_power("^2").unwrap_err();
629        assert!(err.contains("Missing unit symbol"));
630    }
631}