Skip to main content

rfham_core/
conversions.rs

1use std::fmt::Display;
2
3use num_rational::Rational32;
4use num_traits::{
5    ConstZero,
6    cast::{FromPrimitive, ToPrimitive},
7};
8
9#[derive(Clone, Copy, Debug, Default, PartialEq)]
10pub struct LengthInFeet {
11    feet: u32,
12    inches: f32,
13}
14
15#[derive(Clone, Copy, Debug, Default, PartialEq)]
16pub enum Units {
17    #[default]
18    Metric,
19    Imperial,
20}
21
22impl Display for LengthInFeet {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(
25            f,
26            "{}",
27            if f.alternate() {
28                if let Some((whole, fractional)) = self.inches_only_fractional() {
29                    format!(
30                        "{}′{}",
31                        self.feet,
32                        match (whole, fractional) {
33                            (0, z) if z == Rational32::ZERO => String::default(),
34                            (whole, z) if z == Rational32::ZERO => format!(" {whole}″"),
35                            (0, fractional) => format!(" {fractional}″"),
36                            (whole, fractional) => format!(" {whole} {fractional}″"),
37                        }
38                    )
39                } else {
40                    format!("{}′ {}″", self.feet, self.inches)
41                }
42            } else {
43                format!(
44                    "{}′{}",
45                    self.feet,
46                    if self.inches != f32::ZERO {
47                        format!(" {}″", self.inches)
48                    } else {
49                        String::default()
50                    }
51                )
52            }
53        )
54    }
55}
56
57impl PartialOrd for LengthInFeet {
58    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
59        match self.feet.partial_cmp(&other.feet) {
60            Some(core::cmp::Ordering::Equal) => {}
61            ord => return ord,
62        }
63        self.inches.partial_cmp(&other.inches)
64    }
65}
66
67impl LengthInFeet {
68    pub fn new(feet: f64) -> Self {
69        let uint_feet = feet.floor() as u32;
70        let float_inches = feet.fract() as f32;
71        println!(
72            "u32: {uint_feet}, f32: {float_inches}/{}",
73            float_inches * 12.0
74        );
75        Self::feet_and_inches(uint_feet, float_inches * 12.0)
76    }
77
78    pub fn inches(inches: f64) -> Self {
79        let feet = (inches / 12.0) as u32;
80        let float_inches = inches.rem_euclid(12.0) as f32;
81        Self::feet_and_inches(feet, float_inches)
82    }
83
84    pub fn feet_and_inches(feet: u32, inches: f32) -> Self {
85        Self { feet, inches }
86    }
87    pub fn feet_and_inches_fractional(
88        feet: u32,
89        inches: u32,
90        fractional: Rational32,
91    ) -> Option<Self> {
92        (fractional + Rational32::from_u32(inches).unwrap())
93            .to_f32()
94            .map(|inches| Self::feet_and_inches(feet, inches))
95    }
96
97    pub fn feet_only(&self) -> u32 {
98        self.feet
99    }
100
101    pub fn inches_only_decimal(&self) -> f32 {
102        self.inches
103    }
104
105    pub fn inches_only_fractional(&self) -> Option<(u32, Rational32)> {
106        if let Some(fractional) = Rational32::from_f32(self.inches.fract()) {
107            let uint_inches = self.inches.floor() as u32;
108            Some((uint_inches, fractional))
109        } else {
110            None
111        }
112    }
113}
114
115#[cfg(test)]
116mod test {
117    use crate::conversions::LengthInFeet;
118
119    #[test]
120    fn test_construct_feet() {
121        assert_eq!(
122            LengthInFeet::new(0.0),
123            LengthInFeet {
124                feet: 0,
125                inches: 0.0
126            }
127        );
128        assert_eq!(LengthInFeet::new(0.0).to_string(), "0′".to_string());
129        assert_eq!(format!("{:#}", LengthInFeet::new(0.0)), "0′".to_string());
130
131        assert_eq!(
132            LengthInFeet::new(1.5),
133            LengthInFeet {
134                feet: 1,
135                inches: 6.0
136            }
137        );
138        assert_eq!(LengthInFeet::new(1.5).to_string(), "1′ 6″".to_string());
139        assert_eq!(format!("{:#}", LengthInFeet::new(1.5)), "1′ 6″".to_string());
140
141        assert_eq!(
142            LengthInFeet::new(2.55),
143            LengthInFeet {
144                feet: 2,
145                inches: 6.6000004
146            }
147        );
148        assert_eq!(
149            LengthInFeet::new(2.6875).to_string(),
150            "2′ 8.25″".to_string()
151        );
152        assert_eq!(
153            format!("{:#}", LengthInFeet::new(2.6875)),
154            "2′ 8 1/4″".to_string()
155        );
156    }
157}