1use num_rational::Ratio;
4use num_traits::checked_pow;
5
6use crate::{EQSupported, EngineeringQuantity, Error};
7
8impl<T: EQSupported<T> + num_integer::Integer + std::convert::From<EngineeringQuantity<T>>>
12 TryFrom<EngineeringQuantity<T>> for Ratio<T>
13{
14 type Error = Error;
15
16 fn try_from(value: EngineeringQuantity<T>) -> Result<Self, Self::Error> {
17 Ok(if value.exponent >= 0 {
18 let result: T = value.into();
20 Ratio::new(Into::<T>::into(result), T::ONE)
21 } else {
22 let denom: T = checked_pow(T::EXPONENT_BASE, value.exponent.unsigned_abs().into())
23 .ok_or(Error::Underflow)?;
24 Ratio::new(value.significand, denom)
25 })
26 }
27}
28
29impl<T: EQSupported<T>> TryFrom<Ratio<T>> for EngineeringQuantity<T>
30where
31 T: num_integer::Integer,
32{
33 type Error = Error;
34
35 fn try_from(value: Ratio<T>) -> Result<Self, Self::Error> {
37 let (num, mut denom) = value.into_raw();
38 let (sig, exp) = if denom == T::ONE {
39 (num, 0i8)
40 } else {
41 let mut exp = 0i8;
42 loop {
44 let (div, rem) = denom.div_rem(&T::EXPONENT_BASE);
45 if div == T::ZERO || rem != T::ZERO {
46 break;
47 }
48 exp -= 1;
49 denom = div;
50 }
51
52 let (scale, rem) = T::EXPONENT_BASE.div_rem(&denom);
54 if rem != T::ZERO {
55 return Err(Error::ImpreciseConversion);
56 }
57 if scale == T::EXPONENT_BASE {
60 (num, exp)
61 } else {
62 (num * scale, exp - 1)
63 }
64 };
65 EngineeringQuantity::from_raw(sig, exp)
66 }
67}
68
69impl<T: EQSupported<T>> TryFrom<EngineeringQuantity<T>> for f64
73where
74 Ratio<T>: num_traits::ToPrimitive,
75 Ratio<T>: TryFrom<EngineeringQuantity<T>, Error = crate::Error>,
76{
77 type Error = Error;
78
79 fn try_from(value: EngineeringQuantity<T>) -> Result<Self, Self::Error> {
80 use num_traits::ToPrimitive as _;
81 let r = Ratio::<T>::try_from(value)?;
82 r.to_f64().ok_or(Error::ImpreciseConversion)
83 }
84}
85
86#[cfg(test)]
89mod test {
90 use std::str::FromStr as _;
91
92 use super::EngineeringQuantity as EQ;
93 use super::Error;
94 use num_rational::Ratio;
95 use num_traits::ToPrimitive;
96
97 #[test]
98 fn to_ratio() {
99 for (sig, exp, num, denom) in &[
100 (1i64, 0i8, 1i64, 1i64),
101 (1, 1, 1000, 1),
102 (27, 2, 27_000_000, 1),
103 (1, -1, 1, 1000),
104 (4, -3, 4, 1_000_000_000),
105 (12_345, -1, 12_345, 1000),
106 (9, 6, 9_000_000_000_000_000_000, 1),
107 (-9, -6, -9, 1_000_000_000_000_000_000),
108 ] {
109 let eq = EQ::from_raw(*sig, *exp).unwrap();
110 let ratio: Ratio<i64> = eq.try_into().unwrap();
111 assert_eq!(ratio, Ratio::new(*num, *denom));
112 }
113 }
114
115 #[test]
116 fn to_ratio_errors() {
117 for (sig, exp, err) in &[
118 (1i64, -7, Error::Underflow),
119 (1_000_000i64, -7, Error::Underflow), (1i64, -11, Error::Underflow),
121 ] {
122 let eq = EQ::from_raw_unchecked(*sig, *exp);
123 let ratio = std::convert::TryInto::<Ratio<i64>>::try_into(eq);
124 assert_eq!(ratio, Err(*err), "case: {}, {}", *sig, *exp);
125 }
126 }
127
128 #[test]
129 fn from_ratio() {
130 for (num, denom, sig, exp) in &[
131 (1i64, 1i64, 1i64, 0i8),
132 (1000, 1, 1, 1),
133 (27_000_000, 1, 27, 2),
134 (1, 1000, 1, -1),
135 (4, 1_000_000_000, 4, -3),
136 (12_345, 1000, 12_345, -1),
137 (9_000_000_000_000_000_000, 1, 9, 6),
138 (-9, 1_000_000_000_000_000_000, -9, -6),
139 ] {
140 let ratio = Ratio::new(*num, *denom);
141 let eq: EQ<i64> = ratio.try_into().unwrap();
142 let expected = EQ::from_raw(*sig, *exp).unwrap();
143 assert_eq!(eq, expected, "inputs: {num:?}, {denom:?}",);
144 }
145 }
146
147 #[test]
148 fn from_ratio_errors() {
149 let ratio = Ratio::new(1, 333);
150 let result = EQ::<i64>::try_from(ratio).unwrap_err();
151 assert_eq!(result, Error::ImpreciseConversion);
152 }
153
154 const FLOAT_TEST_CASES: &[(&str, f64)] = &[
155 ("42", 42.0),
156 ("1m", 0.001),
157 ("1001m", 1.001),
158 ("1001100m", 1001.1),
159 ("1001100u", 1.001_1),
160 ("43M5", 43_500_000.0),
161 ("1a", 0.000_000_000_000_000_001),
162 ];
163
164 #[test]
165 fn to_f64() {
166 use assertables::assert_in_epsilon;
167
168 for (s, expected) in FLOAT_TEST_CASES {
169 let eq = EQ::<i64>::from_str(s).unwrap();
170 let f = eq.to_f64().unwrap();
171 assert_in_epsilon!(f, *expected, f64::EPSILON);
172 }
173
174 for s in &["1z", "1y", "1r", "1q"] {
175 let eq = EQ::<i64>::from_str(s);
176 assert_eq!(eq, Err(Error::Underflow));
177 }
178 }
179 #[test]
180 #[allow(clippy::cast_possible_truncation)]
181 fn to_f32() {
182 use assertables::assert_in_epsilon;
183
184 for (s, expected) in FLOAT_TEST_CASES {
185 let eq = EQ::<i64>::from_str(s).unwrap();
186 let f = eq.to_f32().unwrap();
187 assert_in_epsilon!(f, (*expected) as f32, f32::EPSILON);
188 }
189 }
190}