rfham_core/
conversions.rs1use 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}