mathengine_units/
lib.rs

1pub mod length;
2pub mod temperature;
3
4use std::fmt;
5
6/// Common behavior for all unit types (m, cm, F, C, etc.)
7pub trait UnitType: Copy + PartialEq + std::fmt::Debug + 'static {
8    /// Get the canonical string representation (e.g., "m", "cm", "F")
9    fn canonical_string(&self) -> &'static str;
10
11    /// Parse a unit string into this unit type
12    fn parse(s: &str) -> Result<Self, UnitError>;
13
14    /// Get the dimension name for this unit type
15    fn dimension_name() -> &'static str;
16}
17
18/// Conversion logic for each unit family
19pub trait UnitConversion<U: UnitType> {
20    /// Convert a value to the base unit for this dimension
21    fn to_base_value(unit: U, value: f64) -> f64;
22
23    /// Convert a base unit value to the target unit
24    fn from_base_value(base_value: f64, unit: U) -> f64;
25
26    /// Get the base unit for this dimension
27    fn base_unit() -> U;
28
29    /// Direct conversion between units (for precision), returns None if not available
30    fn convert_direct(_from: U, _to: U, _value: f64) -> Option<f64> {
31        None // Default: no direct conversion available
32    }
33}
34
35/// Generic dimension type that eliminates all duplication
36pub struct Dimension<U: UnitType> {
37    value: f64,
38    unit: U,
39}
40
41impl<U: UnitType> Dimension<U>
42where
43    Self: UnitConversion<U>
44{
45    /// Create a new dimension with the given value and unit
46    pub fn new(value: f64, unit: U) -> Self {
47        Self { value, unit }
48    }
49
50    /// Create a dimension from a unit string and value
51    pub fn from_unit(unit_str: &str, value: f64) -> Result<Self, UnitError> {
52        let unit = U::parse(unit_str)?;
53        Ok(Self::new(value, unit))
54    }
55
56    /// Convert this dimension to a different unit
57    pub fn convert_to(&self, target: U) -> Self {
58        let new_value = if self.unit == target {
59            self.value
60        } else if let Some(direct_value) = Self::convert_direct(self.unit, target, self.value) {
61            // Use direct conversion for precision when available
62            direct_value
63        } else {
64            // Fall back to base unit conversion
65            let base_value = Self::to_base_value(self.unit, self.value);
66            Self::from_base_value(base_value, target)
67        };
68
69        Self::new(new_value, target)
70    }
71
72    /// Get the numeric value
73    pub fn value(&self) -> f64 {
74        self.value
75    }
76
77    /// Get the unit
78    pub fn unit(&self) -> U {
79        self.unit
80    }
81
82    /// Parse a unit string into the unit type
83    pub fn parse_unit(unit_str: &str) -> Result<U, UnitError> {
84        U::parse(unit_str)
85    }
86
87    /// Convert a value between units (static method)
88    pub fn convert_value(from_unit: U, to_unit: U, value: f64) -> f64 {
89        if from_unit == to_unit {
90            value
91        } else if let Some(direct_value) = Self::convert_direct(from_unit, to_unit, value) {
92            direct_value
93        } else {
94            let base_value = Self::to_base_value(from_unit, value);
95            Self::from_base_value(base_value, to_unit)
96        }
97    }
98}
99
100impl<U: UnitType> fmt::Display for Dimension<U> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        write!(f, "{}{}", self.value, self.unit.canonical_string())
103    }
104}
105
106
107#[derive(Debug, Clone, PartialEq)]
108pub enum UnitError {
109    UnknownUnit(String),
110}
111
112impl fmt::Display for UnitError {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            UnitError::UnknownUnit(unit) => write!(f, "Unknown unit: '{}'", unit),
116        }
117    }
118}
119
120impl std::error::Error for UnitError {}