1use std::fmt;
10use std::str::FromStr;
11
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum LengthUnit {
17 Mils,
18 Mm,
19 Inches,
20 #[serde(rename = "um")]
21 Um,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26pub enum FreqUnit {
27 Hz,
28 #[serde(rename = "kHz")]
29 KHz,
30 MHz,
31 GHz,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36pub enum CapUnit {
37 F,
38 #[serde(rename = "uF")]
39 UF,
40 #[serde(rename = "nF")]
41 NF,
42 #[serde(rename = "pF")]
43 PF,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48pub enum IndUnit {
49 H,
50 #[serde(rename = "mH")]
51 MH,
52 #[serde(rename = "uH")]
53 UH,
54 #[serde(rename = "nH")]
55 NH,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
60pub enum ResUnit {
61 #[serde(rename = "mOhm")]
62 MOhm,
63 Ohm,
64 #[serde(rename = "kOhm")]
65 KOhm,
66 #[serde(rename = "MOhm")]
67 MOhmMega,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
72pub enum TempUnit {
73 Celsius,
74 Fahrenheit,
75}
76
77pub fn to_mils(value: f64, unit: LengthUnit) -> f64 {
81 match unit {
82 LengthUnit::Mils => value,
83 LengthUnit::Mm => value / 0.0254,
84 LengthUnit::Inches => value * 1000.0,
85 LengthUnit::Um => value / 25.4,
86 }
87}
88
89pub fn from_mils(value: f64, unit: LengthUnit) -> f64 {
91 match unit {
92 LengthUnit::Mils => value,
93 LengthUnit::Mm => value * 0.0254,
94 LengthUnit::Inches => value / 1000.0,
95 LengthUnit::Um => value * 25.4,
96 }
97}
98
99pub fn to_hz(value: f64, unit: FreqUnit) -> f64 {
103 match unit {
104 FreqUnit::Hz => value,
105 FreqUnit::KHz => value * 1e3,
106 FreqUnit::MHz => value * 1e6,
107 FreqUnit::GHz => value * 1e9,
108 }
109}
110
111pub fn from_hz(value: f64, unit: FreqUnit) -> f64 {
113 match unit {
114 FreqUnit::Hz => value,
115 FreqUnit::KHz => value / 1e3,
116 FreqUnit::MHz => value / 1e6,
117 FreqUnit::GHz => value / 1e9,
118 }
119}
120
121pub fn to_farads(value: f64, unit: CapUnit) -> f64 {
125 match unit {
126 CapUnit::F => value,
127 CapUnit::UF => value * 1e-6,
128 CapUnit::NF => value * 1e-9,
129 CapUnit::PF => value * 1e-12,
130 }
131}
132
133pub fn from_farads(value: f64, unit: CapUnit) -> f64 {
135 match unit {
136 CapUnit::F => value,
137 CapUnit::UF => value / 1e-6,
138 CapUnit::NF => value / 1e-9,
139 CapUnit::PF => value / 1e-12,
140 }
141}
142
143pub fn to_henries(value: f64, unit: IndUnit) -> f64 {
147 match unit {
148 IndUnit::H => value,
149 IndUnit::MH => value * 1e-3,
150 IndUnit::UH => value * 1e-6,
151 IndUnit::NH => value * 1e-9,
152 }
153}
154
155pub fn from_henries(value: f64, unit: IndUnit) -> f64 {
157 match unit {
158 IndUnit::H => value,
159 IndUnit::MH => value / 1e-3,
160 IndUnit::UH => value / 1e-6,
161 IndUnit::NH => value / 1e-9,
162 }
163}
164
165pub fn to_celsius(value: f64, unit: TempUnit) -> f64 {
169 match unit {
170 TempUnit::Celsius => value,
171 TempUnit::Fahrenheit => (value - 32.0) * 5.0 / 9.0,
172 }
173}
174
175pub fn from_celsius(value: f64, unit: TempUnit) -> f64 {
177 match unit {
178 TempUnit::Celsius => value,
179 TempUnit::Fahrenheit => value * 9.0 / 5.0 + 32.0,
180 }
181}
182
183#[derive(Debug, Clone, PartialEq, thiserror::Error)]
187pub enum UnitParseError {
188 #[error("invalid number in '{0}'")]
189 InvalidNumber(String),
190
191 #[error("unknown unit suffix in '{0}'")]
192 UnknownSuffix(String),
193
194 #[error("value is not finite")]
195 NotFinite,
196}
197
198fn split_number_suffix(s: &str) -> (&str, &str) {
206 let s = s.trim();
207 let bytes = s.as_bytes();
208 let mut i = 0;
209
210 if i < bytes.len() && (bytes[i] == b'-' || bytes[i] == b'+') {
212 i += 1;
213 }
214
215 while i < bytes.len() && bytes[i].is_ascii_digit() {
217 i += 1;
218 }
219
220 if i < bytes.len() && bytes[i] == b'.' {
222 i += 1;
223 while i < bytes.len() && bytes[i].is_ascii_digit() {
224 i += 1;
225 }
226 }
227
228 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
230 let mut j = i + 1;
231 if j < bytes.len() && (bytes[j] == b'-' || bytes[j] == b'+') {
232 j += 1;
233 }
234 if j < bytes.len() && bytes[j].is_ascii_digit() {
235 i = j;
236 while i < bytes.len() && bytes[i].is_ascii_digit() {
237 i += 1;
238 }
239 }
240 }
241
242 (&s[..i], s[i..].trim_start())
243}
244
245#[derive(Debug, Clone, Copy, PartialEq)]
252pub struct Length(pub f64);
253
254impl Length {
255 pub fn mils(self) -> f64 {
256 self.0
257 }
258}
259
260impl FromStr for Length {
261 type Err = UnitParseError;
262
263 fn from_str(s: &str) -> Result<Self, Self::Err> {
264 let (num, suffix) = split_number_suffix(s);
265 let value: f64 = num
266 .parse()
267 .map_err(|_| UnitParseError::InvalidNumber(s.to_string()))?;
268 if !value.is_finite() {
269 return Err(UnitParseError::NotFinite);
270 }
271 let unit = match suffix.to_lowercase().as_str() {
272 "" | "mil" | "mils" => LengthUnit::Mils,
273 "mm" => LengthUnit::Mm,
274 "in" | "inch" | "inches" => LengthUnit::Inches,
275 "um" | "µm" => LengthUnit::Um,
276 _ => return Err(UnitParseError::UnknownSuffix(s.to_string())),
277 };
278 Ok(Length(to_mils(value, unit)))
279 }
280}
281
282impl fmt::Display for Length {
283 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
284 write!(f, "{}mil", self.0)
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq)]
293pub struct Freq(pub f64);
294
295impl Freq {
296 pub fn hz(self) -> f64 {
297 self.0
298 }
299}
300
301impl FromStr for Freq {
302 type Err = UnitParseError;
303
304 fn from_str(s: &str) -> Result<Self, Self::Err> {
305 let (num, suffix) = split_number_suffix(s);
306 let value: f64 = num
307 .parse()
308 .map_err(|_| UnitParseError::InvalidNumber(s.to_string()))?;
309 if !value.is_finite() {
310 return Err(UnitParseError::NotFinite);
311 }
312 let unit = match suffix.to_lowercase().as_str() {
313 "" | "hz" => FreqUnit::Hz,
314 "khz" => FreqUnit::KHz,
315 "mhz" => FreqUnit::MHz,
316 "ghz" => FreqUnit::GHz,
317 _ => return Err(UnitParseError::UnknownSuffix(s.to_string())),
318 };
319 Ok(Freq(to_hz(value, unit)))
320 }
321}
322
323impl fmt::Display for Freq {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 write!(f, "{}Hz", self.0)
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq)]
334pub struct Capacitance(pub f64);
335
336impl Capacitance {
337 pub fn farads(self) -> f64 {
338 self.0
339 }
340}
341
342impl FromStr for Capacitance {
343 type Err = UnitParseError;
344
345 fn from_str(s: &str) -> Result<Self, Self::Err> {
346 let (num, suffix) = split_number_suffix(s);
347 let value: f64 = num
348 .parse()
349 .map_err(|_| UnitParseError::InvalidNumber(s.to_string()))?;
350 if !value.is_finite() {
351 return Err(UnitParseError::NotFinite);
352 }
353 let norm = suffix.replace('µ', "u").to_lowercase();
354 let unit = match norm.as_str() {
355 "" | "f" => CapUnit::F,
356 "uf" => CapUnit::UF,
357 "nf" => CapUnit::NF,
358 "pf" => CapUnit::PF,
359 _ => return Err(UnitParseError::UnknownSuffix(s.to_string())),
360 };
361 Ok(Capacitance(to_farads(value, unit)))
362 }
363}
364
365impl fmt::Display for Capacitance {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 write!(f, "{}F", self.0)
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq)]
376pub struct Inductance(pub f64);
377
378impl Inductance {
379 pub fn henries(self) -> f64 {
380 self.0
381 }
382}
383
384impl FromStr for Inductance {
385 type Err = UnitParseError;
386
387 fn from_str(s: &str) -> Result<Self, Self::Err> {
388 let (num, suffix) = split_number_suffix(s);
389 let value: f64 = num
390 .parse()
391 .map_err(|_| UnitParseError::InvalidNumber(s.to_string()))?;
392 if !value.is_finite() {
393 return Err(UnitParseError::NotFinite);
394 }
395 let norm = suffix.replace('µ', "u").to_lowercase();
396 let unit = match norm.as_str() {
397 "" | "h" => IndUnit::H,
398 "mh" => IndUnit::MH,
399 "uh" => IndUnit::UH,
400 "nh" => IndUnit::NH,
401 _ => return Err(UnitParseError::UnknownSuffix(s.to_string())),
402 };
403 Ok(Inductance(to_henries(value, unit)))
404 }
405}
406
407impl fmt::Display for Inductance {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 write!(f, "{}H", self.0)
410 }
411}
412
413#[derive(Debug, Clone, Copy, PartialEq)]
419pub struct Temperature(pub f64);
420
421impl Temperature {
422 pub fn celsius(self) -> f64 {
423 self.0
424 }
425}
426
427impl FromStr for Temperature {
428 type Err = UnitParseError;
429
430 fn from_str(s: &str) -> Result<Self, Self::Err> {
431 let (num, suffix) = split_number_suffix(s);
432 let value: f64 = num
433 .parse()
434 .map_err(|_| UnitParseError::InvalidNumber(s.to_string()))?;
435 if !value.is_finite() {
436 return Err(UnitParseError::NotFinite);
437 }
438 let unit = match suffix {
439 "" | "C" | "°C" | "degC" => TempUnit::Celsius,
440 "F" | "°F" | "degF" => TempUnit::Fahrenheit,
441 _ => return Err(UnitParseError::UnknownSuffix(s.to_string())),
442 };
443 Ok(Temperature(to_celsius(value, unit)))
444 }
445}
446
447impl fmt::Display for Temperature {
448 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449 write!(f, "{}°C", self.0)
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456
457 #[test]
458 fn length_roundtrip() {
459 let mils = 100.0;
460 for unit in [LengthUnit::Mils, LengthUnit::Mm, LengthUnit::Inches, LengthUnit::Um] {
461 let converted = from_mils(mils, unit);
462 let back = to_mils(converted, unit);
463 assert!((back - mils).abs() < 1e-10, "roundtrip failed for {unit:?}");
464 }
465 }
466
467 #[test]
468 fn known_conversions() {
469 assert!((to_mils(0.0254, LengthUnit::Mm) - 1.0).abs() < 1e-10);
471 assert!((to_mils(1.0, LengthUnit::Inches) - 1000.0).abs() < 1e-10);
473 assert!((to_mils(25.4, LengthUnit::Um) - 1.0).abs() < 1e-10);
475 assert!((to_hz(1.0, FreqUnit::GHz) - 1e9).abs() < 1.0);
477 assert!((to_celsius(32.0, TempUnit::Fahrenheit)).abs() < 1e-10);
479 }
480
481 #[test]
484 fn split_bare_number() {
485 assert_eq!(split_number_suffix("100"), ("100", ""));
486 }
487
488 #[test]
489 fn split_with_suffix() {
490 assert_eq!(split_number_suffix("0.254mm"), ("0.254", "mm"));
491 }
492
493 #[test]
494 fn split_scientific() {
495 assert_eq!(split_number_suffix("1e3mm"), ("1e3", "mm"));
496 assert_eq!(split_number_suffix("1.5E-6nH"), ("1.5E-6", "nH"));
497 }
498
499 #[test]
500 fn split_negative() {
501 assert_eq!(split_number_suffix("-5mil"), ("-5", "mil"));
502 }
503
504 #[test]
505 fn split_whitespace() {
506 assert_eq!(split_number_suffix(" 100mil "), ("100", "mil"));
507 assert_eq!(split_number_suffix("100 mil"), ("100", "mil"));
509 }
510
511 #[test]
514 fn parse_length_bare() {
515 let l: Length = "100".parse().unwrap();
516 assert!((l.mils() - 100.0).abs() < 1e-10);
517 }
518
519 #[test]
520 fn parse_length_mil() {
521 let l: Length = "100mil".parse().unwrap();
522 assert!((l.mils() - 100.0).abs() < 1e-10);
523 let l2: Length = "100mils".parse().unwrap();
524 assert!((l2.mils() - 100.0).abs() < 1e-10);
525 }
526
527 #[test]
528 fn parse_length_mm() {
529 let l: Length = "0.254mm".parse().unwrap();
530 assert!((l.mils() - 10.0).abs() < 1e-6);
531 }
532
533 #[test]
534 fn parse_length_inches() {
535 let l: Length = "0.1in".parse().unwrap();
536 assert!((l.mils() - 100.0).abs() < 1e-10);
537 let l2: Length = "0.1inch".parse().unwrap();
538 assert!((l2.mils() - 100.0).abs() < 1e-10);
539 }
540
541 #[test]
542 fn parse_length_um() {
543 let l: Length = "25.4um".parse().unwrap();
544 assert!((l.mils() - 1.0).abs() < 1e-10);
545 }
546
547 #[test]
548 fn parse_length_um_unicode() {
549 let l: Length = "25.4µm".parse().unwrap();
550 assert!((l.mils() - 1.0).abs() < 1e-10);
551 }
552
553 #[test]
554 fn parse_length_scientific() {
555 let l: Length = "1e3mil".parse().unwrap();
556 assert!((l.mils() - 1000.0).abs() < 1e-10);
557 }
558
559 #[test]
560 fn parse_length_negative() {
561 let l: Length = "-5mil".parse().unwrap();
562 assert!((l.mils() - (-5.0)).abs() < 1e-10);
563 }
564
565 #[test]
566 fn parse_length_case_insensitive() {
567 let l: Length = "10MIL".parse().unwrap();
568 assert!((l.mils() - 10.0).abs() < 1e-10);
569 let l2: Length = "1MM".parse().unwrap();
570 assert!((l2.mils() - to_mils(1.0, LengthUnit::Mm)).abs() < 1e-10);
571 }
572
573 #[test]
574 fn parse_length_errors() {
575 assert!("".parse::<Length>().is_err());
576 assert!("mm".parse::<Length>().is_err());
577 assert!("100ft".parse::<Length>().is_err());
578 assert!("abc".parse::<Length>().is_err());
579 }
580
581 #[test]
584 fn parse_freq_bare() {
585 let f: Freq = "1000000".parse().unwrap();
586 assert!((f.hz() - 1_000_000.0).abs() < 1.0);
587 }
588
589 #[test]
590 fn parse_freq_mhz() {
591 let f: Freq = "100MHz".parse().unwrap();
592 assert!((f.hz() - 100e6).abs() < 1.0);
593 }
594
595 #[test]
596 fn parse_freq_ghz() {
597 let f: Freq = "2.4GHz".parse().unwrap();
598 assert!((f.hz() - 2.4e9).abs() < 1.0);
599 }
600
601 #[test]
602 fn parse_freq_khz() {
603 let f: Freq = "50kHz".parse().unwrap();
604 assert!((f.hz() - 50e3).abs() < 1.0);
605 }
606
607 #[test]
608 fn parse_freq_case_insensitive() {
609 let f: Freq = "1ghz".parse().unwrap();
610 assert!((f.hz() - 1e9).abs() < 1.0);
611 }
612
613 #[test]
614 fn parse_freq_errors() {
615 assert!("".parse::<Freq>().is_err());
616 assert!("Hz".parse::<Freq>().is_err());
617 assert!("100rpm".parse::<Freq>().is_err());
618 }
619
620 #[test]
623 fn parse_cap_pf() {
624 let c: Capacitance = "100pF".parse().unwrap();
625 assert!((c.farads() - 100e-12).abs() < 1e-20);
626 }
627
628 #[test]
629 fn parse_cap_uf_unicode() {
630 let c: Capacitance = "10µF".parse().unwrap();
631 assert!((c.farads() - 10e-6).abs() < 1e-14);
632 }
633
634 #[test]
637 fn parse_ind_nh() {
638 let i: Inductance = "10nH".parse().unwrap();
639 assert!((i.henries() - 10e-9).abs() < 1e-18);
640 }
641
642 #[test]
643 fn parse_ind_uh_unicode() {
644 let i: Inductance = "4.7µH".parse().unwrap();
645 assert!((i.henries() - 4.7e-6).abs() < 1e-14);
646 }
647
648 #[test]
651 fn parse_temp_celsius() {
652 let t: Temperature = "25C".parse().unwrap();
653 assert!((t.celsius() - 25.0).abs() < 1e-10);
654 let t2: Temperature = "25°C".parse().unwrap();
655 assert!((t2.celsius() - 25.0).abs() < 1e-10);
656 let t3: Temperature = "25degC".parse().unwrap();
657 assert!((t3.celsius() - 25.0).abs() < 1e-10);
658 }
659
660 #[test]
661 fn parse_temp_fahrenheit() {
662 let t: Temperature = "77F".parse().unwrap();
663 assert!((t.celsius() - 25.0).abs() < 0.01);
664 let t2: Temperature = "77°F".parse().unwrap();
665 assert!((t2.celsius() - 25.0).abs() < 0.01);
666 }
667
668 #[test]
669 fn parse_temp_bare_is_celsius() {
670 let t: Temperature = "100".parse().unwrap();
671 assert!((t.celsius() - 100.0).abs() < 1e-10);
672 }
673
674 #[test]
677 fn display_length() {
678 assert_eq!(format!("{}", Length(100.0)), "100mil");
679 }
680
681 #[test]
682 fn display_freq() {
683 assert_eq!(format!("{}", Freq(1e9)), "1000000000Hz");
684 }
685
686 #[test]
687 fn display_temp() {
688 assert_eq!(format!("{}", Temperature(25.0)), "25°C");
689 }
690}