1use anyhow::Result;
2use anyhow::anyhow;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum UnitCategory {
6 Angle,
7 Area,
8 ConcentrationMass,
9 Duration,
10 ElectricCharge,
11 ElectricCurrent,
12 ElectricPotentialDifference,
13 ElectricResistance,
14 Energy,
15 Frequency,
16 FuelEfficiency,
17 InformationStorage,
18 Length,
19 Mass,
20 Power,
21 Pressure,
22 Speed,
23 Temperature,
24 Volume,
25}
26
27impl UnitCategory {
28 pub fn name(&self) -> &'static str {
29 match self {
30 Self::Angle => "angle",
31 Self::Area => "area",
32 Self::ConcentrationMass => "concentration of mass",
33 Self::Duration => "duration",
34 Self::ElectricCharge => "electric charge",
35 Self::ElectricCurrent => "electric current",
36 Self::ElectricPotentialDifference => "electric potential difference",
37 Self::ElectricResistance => "electric resistance",
38 Self::Energy => "energy",
39 Self::Frequency => "frequency",
40 Self::FuelEfficiency => "fuel efficiency",
41 Self::InformationStorage => "information storage",
42 Self::Length => "length",
43 Self::Mass => "mass",
44 Self::Power => "power",
45 Self::Pressure => "pressure",
46 Self::Speed => "speed",
47 Self::Temperature => "temperature",
48 Self::Volume => "volume",
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
54pub enum ConversionType {
55 Linear { coefficient: f64 },
57 Reciprocal { coefficient: f64 },
59 Temperature {
61 to_kelvin: fn(f64) -> f64,
62 from_kelvin: fn(f64) -> f64,
63 },
64}
65
66#[derive(Debug, Clone)]
67pub struct Unit {
68 pub category: UnitCategory,
69 pub identifiers: &'static [&'static str],
70 pub conversion: ConversionType,
71}
72
73impl Unit {
74 fn new_linear(
75 category: UnitCategory,
76 identifiers: &'static [&'static str],
77 coefficient: f64,
78 ) -> Self {
79 Self {
80 category,
81 identifiers,
82 conversion: ConversionType::Linear { coefficient },
83 }
84 }
85
86 fn new_reciprocal(
87 category: UnitCategory,
88 identifiers: &'static [&'static str],
89 coefficient: f64,
90 ) -> Self {
91 Self {
92 category,
93 identifiers,
94 conversion: ConversionType::Reciprocal { coefficient },
95 }
96 }
97
98 fn new_temperature(
99 identifiers: &'static [&'static str],
100 to_kelvin: fn(f64) -> f64,
101 from_kelvin: fn(f64) -> f64,
102 ) -> Self {
103 Self {
104 category: UnitCategory::Temperature,
105 identifiers,
106 conversion: ConversionType::Temperature {
107 to_kelvin,
108 from_kelvin,
109 },
110 }
111 }
112
113 pub fn matches(&self, identifier: &str) -> bool {
114 if self.matches_exact(identifier) {
115 return true;
116 }
117 let id_lower = identifier.to_lowercase();
118 self.identifiers
119 .iter()
120 .any(|&i| i.to_lowercase() == id_lower)
121 }
122
123 pub fn matches_exact(&self, identifier: &str) -> bool {
124 self.identifiers.iter().any(|&i| i == identifier)
125 }
126
127 pub fn convert_to_base(&self, value: f64) -> f64 {
128 match &self.conversion {
129 ConversionType::Linear { coefficient } => value * coefficient,
130 ConversionType::Reciprocal { coefficient } => {
131 if value == 0.0 {
132 f64::INFINITY
133 } else {
134 coefficient / value
135 }
136 }
137 ConversionType::Temperature { to_kelvin, .. } => to_kelvin(value),
138 }
139 }
140
141 pub fn convert_from_base(&self, value: f64) -> f64 {
142 match &self.conversion {
143 ConversionType::Linear { coefficient } => value / coefficient,
144 ConversionType::Reciprocal { coefficient } => {
145 if value == 0.0 {
146 f64::INFINITY
147 } else {
148 coefficient / value
149 }
150 }
151 ConversionType::Temperature { from_kelvin, .. } => from_kelvin(value),
152 }
153 }
154}
155
156fn celsius_to_kelvin(c: f64) -> f64 {
158 c + 273.15
159}
160
161fn kelvin_to_celsius(k: f64) -> f64 {
162 k - 273.15
163}
164
165fn fahrenheit_to_kelvin(f: f64) -> f64 {
166 (f - 32.0) * 5.0 / 9.0 + 273.15
167}
168
169fn kelvin_to_fahrenheit(k: f64) -> f64 {
170 (k - 273.15) * 9.0 / 5.0 + 32.0
171}
172
173fn kelvin_to_kelvin(k: f64) -> f64 {
174 k
175}
176
177pub fn get_all_units() -> Vec<Unit> {
178 vec![
179 Unit::new_temperature(&["kelvin", "k"], kelvin_to_kelvin, kelvin_to_kelvin),
181 Unit::new_temperature(
182 &["celsius", "°c", "c"],
183 celsius_to_kelvin,
184 kelvin_to_celsius,
185 ),
186 Unit::new_temperature(
187 &["fahrenheit", "°f", "f"],
188 fahrenheit_to_kelvin,
189 kelvin_to_fahrenheit,
190 ),
191 Unit::new_linear(
193 UnitCategory::Length,
194 &["meters", "meter", "m", "metres", "metre"],
195 1.0,
196 ),
197 Unit::new_linear(
198 UnitCategory::Length,
199 &["kilometers", "kilometer", "km", "kilometres", "kilometre"],
200 1000.0,
201 ),
202 Unit::new_linear(
203 UnitCategory::Length,
204 &[
205 "centimeters",
206 "centimeter",
207 "cm",
208 "centimetres",
209 "centimetre",
210 ],
211 0.01,
212 ),
213 Unit::new_linear(
214 UnitCategory::Length,
215 &[
216 "millimeters",
217 "millimeter",
218 "mm",
219 "millimetres",
220 "millimetre",
221 ],
222 0.001,
223 ),
224 Unit::new_linear(
225 UnitCategory::Length,
226 &[
227 "micrometers",
228 "micrometer",
229 "μm",
230 "micrometres",
231 "micrometre",
232 "um",
233 ],
234 1e-6,
235 ),
236 Unit::new_linear(
237 UnitCategory::Length,
238 &["nanometers", "nanometer", "nm", "nanometres", "nanometre"],
239 1e-9,
240 ),
241 Unit::new_linear(
242 UnitCategory::Length,
243 &["picometers", "picometer", "pm", "picometres", "picometre"],
244 1e-12,
245 ),
246 Unit::new_linear(UnitCategory::Length, &["inches", "in", "inch"], 0.0254),
247 Unit::new_linear(UnitCategory::Length, &["feet", "ft", "foot"], 0.3048),
248 Unit::new_linear(UnitCategory::Length, &["yards", "yd", "yard"], 0.9144),
249 Unit::new_linear(UnitCategory::Length, &["miles", "mi", "mile"], 1609.344),
250 Unit::new_linear(
251 UnitCategory::Length,
252 &["scandinavian miles", "scandinavian mile"],
253 10000.0,
254 ),
255 Unit::new_linear(
256 UnitCategory::Length,
257 &["light years", "light year", "ly"],
258 9.460_730_472_580_8e15,
259 ),
260 Unit::new_linear(
261 UnitCategory::Length,
262 &["nautical miles", "nautical mile", "nmi"],
263 1852.0,
264 ),
265 Unit::new_linear(UnitCategory::Length, &["fathoms", "fathom"], 1.8288),
266 Unit::new_linear(UnitCategory::Length, &["furlongs", "furlong"], 201.168),
267 Unit::new_linear(
268 UnitCategory::Length,
269 &["astronomical units", "astronomical unit", "au"],
270 149_597_870_700.0,
271 ),
272 Unit::new_linear(
273 UnitCategory::Length,
274 &["parsecs", "parsec", "pc"],
275 3.085_677_581_467_191_6e16,
276 ),
277 Unit::new_linear(
278 UnitCategory::Length,
279 &["megameters", "megameter", "Mm"],
280 1e6,
281 ),
282 Unit::new_linear(
283 UnitCategory::Length,
284 &["hectometers", "hectometer", "hm"],
285 100.0,
286 ),
287 Unit::new_linear(
288 UnitCategory::Length,
289 &["decameters", "decameter", "dam"],
290 10.0,
291 ),
292 Unit::new_linear(
293 UnitCategory::Length,
294 &["decimeters", "decimeter", "dm"],
295 0.1,
296 ),
297 Unit::new_linear(
298 UnitCategory::Length,
299 &["thousandths of an inch", "thou", "mil"],
300 0.0000254,
301 ),
302 Unit::new_linear(UnitCategory::Mass, &["kilograms", "kg", "kilogram"], 1.0),
304 Unit::new_linear(UnitCategory::Mass, &["grams", "g", "gram"], 0.001),
305 Unit::new_linear(UnitCategory::Mass, &["milligrams", "mg", "milligram"], 1e-6),
306 Unit::new_linear(
307 UnitCategory::Mass,
308 &["micrograms", "μg", "microgram", "ug"],
309 1e-9,
310 ),
311 Unit::new_linear(UnitCategory::Mass, &["nanograms", "ng", "nanogram"], 1e-12),
312 Unit::new_linear(UnitCategory::Mass, &["picograms", "pg", "picogram"], 1e-15),
313 Unit::new_linear(
314 UnitCategory::Mass,
315 &["ounces", "oz", "ounce"],
316 0.028349523125,
317 ),
318 Unit::new_linear(
319 UnitCategory::Mass,
320 &["pounds", "lb", "lbs", "pound"],
321 0.45359237,
322 ),
323 Unit::new_linear(UnitCategory::Mass, &["stones", "st", "stone"], 6.35029318),
324 Unit::new_linear(
325 UnitCategory::Mass,
326 &["metric tons", "metric ton", "tonnes", "tonne", "t"],
327 1000.0,
328 ),
329 Unit::new_linear(
330 UnitCategory::Mass,
331 &["short tons", "short ton", "tons", "ton"],
332 907.18474,
333 ),
334 Unit::new_linear(UnitCategory::Mass, &["carats", "ct", "carat"], 0.0002),
335 Unit::new_linear(
336 UnitCategory::Mass,
337 &["troy ounces", "troy ounce", "oz t"],
338 0.0311034768,
339 ),
340 Unit::new_linear(UnitCategory::Mass, &["slugs", "slug"], 14.593903),
341 Unit::new_linear(
343 UnitCategory::Area,
344 &[
345 "square meters",
346 "square meter",
347 "m²",
348 "m2",
349 "square metres",
350 "square metre",
351 ],
352 1.0,
353 ),
354 Unit::new_linear(
355 UnitCategory::Area,
356 &[
357 "square kilometers",
358 "square kilometer",
359 "km²",
360 "km2",
361 "square kilometres",
362 "square kilometre",
363 ],
364 1e6,
365 ),
366 Unit::new_linear(
367 UnitCategory::Area,
368 &[
369 "square centimeters",
370 "square centimeter",
371 "cm²",
372 "cm2",
373 "square centimetres",
374 "square centimetre",
375 ],
376 1e-4,
377 ),
378 Unit::new_linear(
379 UnitCategory::Area,
380 &[
381 "square millimeters",
382 "square millimeter",
383 "mm²",
384 "mm2",
385 "square millimetres",
386 "square millimetre",
387 ],
388 1e-6,
389 ),
390 Unit::new_linear(
391 UnitCategory::Area,
392 &[
393 "square micrometers",
394 "square micrometer",
395 "μm²",
396 "μm2",
397 "square micrometres",
398 "square micrometre",
399 "um2",
400 ],
401 1e-12,
402 ),
403 Unit::new_linear(
404 UnitCategory::Area,
405 &[
406 "square nanometers",
407 "square nanometer",
408 "nm²",
409 "nm2",
410 "square nanometres",
411 "square nanometre",
412 ],
413 1e-18,
414 ),
415 Unit::new_linear(
416 UnitCategory::Area,
417 &["square inches", "in²", "in2", "square inch"],
418 0.00064516,
419 ),
420 Unit::new_linear(
421 UnitCategory::Area,
422 &["square feet", "ft²", "ft2", "square foot"],
423 0.09290304,
424 ),
425 Unit::new_linear(
426 UnitCategory::Area,
427 &["square yards", "yd²", "yd2", "square yard"],
428 0.83612736,
429 ),
430 Unit::new_linear(
431 UnitCategory::Area,
432 &["square miles", "mi²", "mi2", "square mile"],
433 2_589_988.110_336,
434 ),
435 Unit::new_linear(UnitCategory::Area, &["acres", "acre", "ac"], 4046.856_422_4),
436 Unit::new_linear(UnitCategory::Area, &["ares", "are", "a"], 100.0),
437 Unit::new_linear(UnitCategory::Area, &["hectares", "hectare", "ha"], 10000.0),
438 Unit::new_linear(
439 UnitCategory::Area,
440 &["square megameters", "square megameter", "Mm²", "Mm2"],
441 1e12,
442 ),
443 Unit::new_linear(
445 UnitCategory::Volume,
446 &["liters", "l", "liter", "litre", "litres"],
447 1.0,
448 ),
449 Unit::new_linear(
450 UnitCategory::Volume,
451 &[
452 "milliliters",
453 "ml",
454 "milliliter",
455 "millilitre",
456 "millilitres",
457 ],
458 0.001,
459 ),
460 Unit::new_linear(
461 UnitCategory::Volume,
462 &[
463 "centiliters",
464 "cl",
465 "centiliter",
466 "centilitre",
467 "centilitres",
468 ],
469 0.01,
470 ),
471 Unit::new_linear(
472 UnitCategory::Volume,
473 &["deciliters", "dl", "deciliter", "decilitre", "decilitres"],
474 0.1,
475 ),
476 Unit::new_linear(
477 UnitCategory::Volume,
478 &["kiloliters", "kl", "kiloliter", "kilolitre", "kilolitres"],
479 1000.0,
480 ),
481 Unit::new_linear(
482 UnitCategory::Volume,
483 &["megaliter", "megaliters", "ML"],
484 1e6,
485 ),
486 Unit::new_linear(
487 UnitCategory::Volume,
488 &[
489 "cubic meters",
490 "cubic meter",
491 "m³",
492 "m3",
493 "cubic metres",
494 "cubic metre",
495 ],
496 1000.0,
497 ),
498 Unit::new_linear(
499 UnitCategory::Volume,
500 &[
501 "cubic kilometers",
502 "cubic kilometer",
503 "km³",
504 "km3",
505 "cubic kilometres",
506 "cubic kilometre",
507 ],
508 1e12,
509 ),
510 Unit::new_linear(
511 UnitCategory::Volume,
512 &[
513 "cubic centimeters",
514 "cubic centimeter",
515 "cm³",
516 "cm3",
517 "cc",
518 "cubic centimetres",
519 "cubic centimetre",
520 ],
521 0.001,
522 ),
523 Unit::new_linear(
524 UnitCategory::Volume,
525 &[
526 "cubic millimeters",
527 "cubic millimeter",
528 "mm³",
529 "mm3",
530 "cubic millimetres",
531 "cubic millimetre",
532 ],
533 1e-6,
534 ),
535 Unit::new_linear(
536 UnitCategory::Volume,
537 &["cubic inches", "in³", "in3", "cubic inch"],
538 0.016387064,
539 ),
540 Unit::new_linear(
541 UnitCategory::Volume,
542 &["cubic feet", "ft³", "ft3", "cubic foot"],
543 28.316846592,
544 ),
545 Unit::new_linear(
546 UnitCategory::Volume,
547 &["cubic yards", "yd³", "yd3", "cubic yard"],
548 764.554857984,
549 ),
550 Unit::new_linear(
551 UnitCategory::Volume,
552 &["cubic miles", "mi³", "mi3", "cubic mile"],
553 4.168_181_825_440_58e12,
554 ),
555 Unit::new_linear(
556 UnitCategory::Volume,
557 &["acre feet", "acre foot", "acre ft"],
558 1_233_481.837_547_52,
559 ),
560 Unit::new_linear(
561 UnitCategory::Volume,
562 &["bushels", "bushel", "bsh"],
563 35.23907016688,
564 ),
565 Unit::new_linear(
566 UnitCategory::Volume,
567 &["teaspoons", "tsp", "teaspoon"],
568 0.00492892159375,
569 ),
570 Unit::new_linear(
571 UnitCategory::Volume,
572 &["tablespoons", "tbsp", "tablespoon"],
573 0.01478676478125,
574 ),
575 Unit::new_linear(
576 UnitCategory::Volume,
577 &["fluid ounces", "fl oz", "fluid ounce"],
578 0.0295735295625,
579 ),
580 Unit::new_linear(UnitCategory::Volume, &["cups", "cup"], 0.236_588_236_5),
581 Unit::new_linear(UnitCategory::Volume, &["pints", "pt", "pint"], 0.473176473),
582 Unit::new_linear(
583 UnitCategory::Volume,
584 &["quarts", "qt", "quart"],
585 0.946352946,
586 ),
587 Unit::new_linear(
588 UnitCategory::Volume,
589 &["gallons", "gal", "gallon"],
590 3.785411784,
591 ),
592 Unit::new_linear(
593 UnitCategory::Volume,
594 &["imperial teaspoons", "imperial teaspoon", "imp tsp"],
595 0.00591938802083,
596 ),
597 Unit::new_linear(
598 UnitCategory::Volume,
599 &["imperial tablespoons", "imperial tablespoon", "imp tbsp"],
600 0.0177581640625,
601 ),
602 Unit::new_linear(
603 UnitCategory::Volume,
604 &["imperial fluid ounces", "imperial fluid ounce", "imp fl oz"],
605 0.0284130625,
606 ),
607 Unit::new_linear(
608 UnitCategory::Volume,
609 &["imperial pints", "imperial pint", "imp pt"],
610 0.56826125,
611 ),
612 Unit::new_linear(
613 UnitCategory::Volume,
614 &["imperial quarts", "imperial quart", "imp qt"],
615 1.1365225,
616 ),
617 Unit::new_linear(
618 UnitCategory::Volume,
619 &["imperial gallons", "imperial gallon", "imp gal"],
620 4.54609,
621 ),
622 Unit::new_linear(UnitCategory::Volume, &["metric cups", "metric cup"], 0.25),
623 Unit::new_linear(UnitCategory::Angle, &["degrees", "deg", "°", "degree"], 1.0),
625 Unit::new_linear(
626 UnitCategory::Angle,
627 &["arc minutes", "arcminutes", "arcmin", "′", "arc minute"],
628 1.0 / 60.0,
629 ),
630 Unit::new_linear(
631 UnitCategory::Angle,
632 &["arc seconds", "arcseconds", "arcsec", "″", "arc second"],
633 1.0 / 3600.0,
634 ),
635 Unit::new_linear(
636 UnitCategory::Angle,
637 &["radians", "rad", "radian"],
638 180.0 / std::f64::consts::PI,
639 ),
640 Unit::new_linear(
641 UnitCategory::Angle,
642 &["gradians", "grad", "gradian", "gon"],
643 0.9,
644 ),
645 Unit::new_linear(
646 UnitCategory::Angle,
647 &["revolutions", "rev", "revolution", "rotation"],
648 360.0,
649 ),
650 Unit::new_linear(
652 UnitCategory::Duration,
653 &["seconds", "s", "sec", "second"],
654 1.0,
655 ),
656 Unit::new_linear(
657 UnitCategory::Duration,
658 &["milliseconds", "ms", "millisecond"],
659 0.001,
660 ),
661 Unit::new_linear(
662 UnitCategory::Duration,
663 &["microseconds", "μs", "microsecond", "us"],
664 1e-6,
665 ),
666 Unit::new_linear(
667 UnitCategory::Duration,
668 &["nanoseconds", "ns", "nanosecond"],
669 1e-9,
670 ),
671 Unit::new_linear(
672 UnitCategory::Duration,
673 &["picoseconds", "ps", "picosecond"],
674 1e-12,
675 ),
676 Unit::new_linear(UnitCategory::Duration, &["minutes", "min", "minute"], 60.0),
677 Unit::new_linear(
678 UnitCategory::Duration,
679 &["hours", "h", "hr", "hour"],
680 3600.0,
681 ),
682 Unit::new_linear(UnitCategory::Duration, &["days", "d", "day"], 86400.0),
683 Unit::new_linear(UnitCategory::Duration, &["weeks", "wk", "week"], 604800.0),
684 Unit::new_linear(
686 UnitCategory::Speed,
687 &["meters per second", "m/s", "mps"],
688 1.0,
689 ),
690 Unit::new_linear(
691 UnitCategory::Speed,
692 &["kilometers per hour", "km/h", "kph", "kmph"],
693 1.0 / 3.6,
694 ),
695 Unit::new_linear(
696 UnitCategory::Speed,
697 &["miles per hour", "mph", "mi/h"],
698 0.44704,
699 ),
700 Unit::new_linear(
701 UnitCategory::Speed,
702 &["knots", "knot", "kn"],
703 0.514_444_444_444_444_5,
704 ),
705 Unit::new_linear(UnitCategory::Energy, &["joules", "j", "joule"], 1.0),
707 Unit::new_linear(
708 UnitCategory::Energy,
709 &["kilojoules", "kj", "kilojoule"],
710 1000.0,
711 ),
712 Unit::new_linear(UnitCategory::Energy, &["calories", "cal", "calorie"], 4.184),
713 Unit::new_linear(
714 UnitCategory::Energy,
715 &["kilocalories", "kcal", "kilocalorie", "food calorie", "Cal"],
716 4184.0,
717 ),
718 Unit::new_linear(
719 UnitCategory::Energy,
720 &["watt hours", "wh", "watt hour"],
721 3600.0,
722 ),
723 Unit::new_linear(
724 UnitCategory::Energy,
725 &["kilowatt hours", "kwh", "kilowatt hour"],
726 3.6e6,
727 ),
728 Unit::new_linear(
729 UnitCategory::Energy,
730 &["electronvolts", "ev", "electronvolt"],
731 1.602176634e-19,
732 ),
733 Unit::new_linear(UnitCategory::Power, &["watts", "w", "watt"], 1.0),
735 Unit::new_linear(
736 UnitCategory::Power,
737 &["milliwatts", "mw", "milliwatt"],
738 0.001,
739 ),
740 Unit::new_linear(
741 UnitCategory::Power,
742 &["microwatts", "μw", "microwatt", "uw"],
743 1e-6,
744 ),
745 Unit::new_linear(UnitCategory::Power, &["nanowatts", "nw", "nanowatt"], 1e-9),
746 Unit::new_linear(UnitCategory::Power, &["picowatts", "pw", "picowatt"], 1e-12),
747 Unit::new_linear(
748 UnitCategory::Power,
749 &["femtowatts", "fw", "femtowatt"],
750 1e-15,
751 ),
752 Unit::new_linear(
753 UnitCategory::Power,
754 &["kilowatts", "kw", "kilowatt"],
755 1000.0,
756 ),
757 Unit::new_linear(UnitCategory::Power, &["megawatts", "MW", "megawatt"], 1e6),
758 Unit::new_linear(UnitCategory::Power, &["gigawatts", "GW", "gigawatt"], 1e9),
759 Unit::new_linear(UnitCategory::Power, &["terawatts", "TW", "terawatt"], 1e12),
760 Unit::new_linear(
761 UnitCategory::Power,
762 &["horsepower", "hp"],
763 745.699_871_582_270_1,
764 ),
765 Unit::new_linear(UnitCategory::Pressure, &["pascals", "pa", "pascal"], 1.0),
767 Unit::new_linear(
768 UnitCategory::Pressure,
769 &["hectopascals", "hpa", "hectopascal"],
770 100.0,
771 ),
772 Unit::new_linear(
773 UnitCategory::Pressure,
774 &["kilopascals", "kpa", "kilopascal"],
775 1000.0,
776 ),
777 Unit::new_linear(
778 UnitCategory::Pressure,
779 &["megapascals", "mpa", "megapascal"],
780 1e6,
781 ),
782 Unit::new_linear(
783 UnitCategory::Pressure,
784 &["gigapascals", "gpa", "gigapascal"],
785 1e9,
786 ),
787 Unit::new_linear(UnitCategory::Pressure, &["bars", "bar"], 100000.0),
788 Unit::new_linear(
789 UnitCategory::Pressure,
790 &["millibars", "mbar", "millibar"],
791 100.0,
792 ),
793 Unit::new_linear(
794 UnitCategory::Pressure,
795 &["millimeters of mercury", "mmhg", "mm hg"],
796 133.322387415,
797 ),
798 Unit::new_linear(
799 UnitCategory::Pressure,
800 &["inches of mercury", "inhg", "in hg"],
801 3386.389,
802 ),
803 Unit::new_linear(
804 UnitCategory::Pressure,
805 &["pounds per square inch", "psi", "lbf/in²"],
806 6894.757293168,
807 ),
808 Unit::new_linear(
809 UnitCategory::Pressure,
810 &["atmospheres", "atm", "atmosphere"],
811 101325.0,
812 ),
813 Unit::new_linear(UnitCategory::Frequency, &["hertz", "hz", "Hz"], 1.0),
815 Unit::new_linear(UnitCategory::Frequency, &["millihertz", "mHz"], 0.001),
816 Unit::new_linear(UnitCategory::Frequency, &["microhertz", "µHz", "uHz"], 1e-6),
817 Unit::new_linear(UnitCategory::Frequency, &["nanohertz", "nHz"], 1e-9),
818 Unit::new_linear(UnitCategory::Frequency, &["kilohertz", "kHz"], 1000.0),
819 Unit::new_linear(UnitCategory::Frequency, &["megahertz", "MHz"], 1e6),
820 Unit::new_linear(UnitCategory::Frequency, &["gigahertz", "GHz"], 1e9),
821 Unit::new_linear(UnitCategory::Frequency, &["terahertz", "THz"], 1e12),
822 Unit::new_linear(
824 UnitCategory::ElectricCharge,
825 &["coulombs", "c", "coulomb"],
826 1.0,
827 ),
828 Unit::new_linear(
829 UnitCategory::ElectricCharge,
830 &["megaampere hours", "MAh"],
831 3.6e9,
832 ),
833 Unit::new_linear(
834 UnitCategory::ElectricCharge,
835 &["kiloampere hours", "kAh"],
836 3.6e6,
837 ),
838 Unit::new_linear(
839 UnitCategory::ElectricCharge,
840 &["ampere hours", "Ah"],
841 3600.0,
842 ),
843 Unit::new_linear(
844 UnitCategory::ElectricCharge,
845 &["milliampere hours", "mAh"],
846 3.6,
847 ),
848 Unit::new_linear(
849 UnitCategory::ElectricCharge,
850 &["microampere hours", "µAh", "uAh"],
851 0.0036,
852 ),
853 Unit::new_linear(
855 UnitCategory::ElectricCurrent,
856 &["amperes", "A", "amp", "amps", "ampere"],
857 1.0,
858 ),
859 Unit::new_linear(UnitCategory::ElectricCurrent, &["megaamperes", "MA"], 1e6),
860 Unit::new_linear(
861 UnitCategory::ElectricCurrent,
862 &["kiloamperes", "kA"],
863 1000.0,
864 ),
865 Unit::new_linear(
866 UnitCategory::ElectricCurrent,
867 &["milliamperes", "mA"],
868 0.001,
869 ),
870 Unit::new_linear(
871 UnitCategory::ElectricCurrent,
872 &["microamperes", "µA", "uA"],
873 1e-6,
874 ),
875 Unit::new_linear(
877 UnitCategory::ElectricPotentialDifference,
878 &["volts", "V", "volt", "v"],
879 1.0,
880 ),
881 Unit::new_linear(
882 UnitCategory::ElectricPotentialDifference,
883 &["megavolts", "MV"],
884 1e6,
885 ),
886 Unit::new_linear(
887 UnitCategory::ElectricPotentialDifference,
888 &["kilovolts", "kV"],
889 1000.0,
890 ),
891 Unit::new_linear(
892 UnitCategory::ElectricPotentialDifference,
893 &["millivolts", "mV"],
894 0.001,
895 ),
896 Unit::new_linear(
897 UnitCategory::ElectricPotentialDifference,
898 &["microvolts", "µV", "uV"],
899 1e-6,
900 ),
901 Unit::new_linear(
903 UnitCategory::ElectricResistance,
904 &["ohms", "Ω", "ohm", "ω"],
905 1.0,
906 ),
907 Unit::new_linear(
908 UnitCategory::ElectricResistance,
909 &["megaohms", "MΩ", "megohm"],
910 1e6,
911 ),
912 Unit::new_linear(
913 UnitCategory::ElectricResistance,
914 &["kiloohms", "kΩ", "kilohm"],
915 1000.0,
916 ),
917 Unit::new_linear(
918 UnitCategory::ElectricResistance,
919 &["milliohms", "mΩ", "milliohm"],
920 0.001,
921 ),
922 Unit::new_linear(
923 UnitCategory::ElectricResistance,
924 &["microohms", "µΩ", "microhm", "uΩ"],
925 1e-6,
926 ),
927 Unit::new_linear(
929 UnitCategory::ConcentrationMass,
930 &["grams per liter", "g/l"],
931 1.0,
932 ),
933 Unit::new_linear(
934 UnitCategory::ConcentrationMass,
935 &["milligrams per deciliter", "mg/dl"],
936 0.01,
937 ),
938 Unit::new_linear(
940 UnitCategory::FuelEfficiency,
941 &["liters per 100 kilometers", "l/100km"],
942 1.0,
943 ),
944 Unit::new_reciprocal(
945 UnitCategory::FuelEfficiency,
946 &["miles per gallon", "mpg"],
947 235.214_583_333_333_34,
948 ),
949 Unit::new_reciprocal(
950 UnitCategory::FuelEfficiency,
951 &["miles per imperial gallon", "imp mpg"],
952 282.480_936_331_822_2,
953 ),
954 Unit::new_linear(UnitCategory::InformationStorage, &["bits", "bit", "b"], 1.0),
956 Unit::new_linear(
957 UnitCategory::InformationStorage,
958 &["nibbles", "nibble"],
959 4.0,
960 ),
961 Unit::new_linear(
962 UnitCategory::InformationStorage,
963 &["bytes", "byte", "B"],
964 8.0,
965 ),
966 Unit::new_linear(
967 UnitCategory::InformationStorage,
968 &["kilobits", "kbit", "kb"],
969 1000.0,
970 ),
971 Unit::new_linear(
972 UnitCategory::InformationStorage,
973 &["megabits", "mbit", "mb"],
974 1e6,
975 ),
976 Unit::new_linear(
977 UnitCategory::InformationStorage,
978 &["gigabits", "gbit", "gb"],
979 1e9,
980 ),
981 Unit::new_linear(
982 UnitCategory::InformationStorage,
983 &["terabits", "tbit", "tb"],
984 1e12,
985 ),
986 Unit::new_linear(
987 UnitCategory::InformationStorage,
988 &["petabits", "pbit", "pb"],
989 1e15,
990 ),
991 Unit::new_linear(
992 UnitCategory::InformationStorage,
993 &["exabits", "ebit", "eb"],
994 1e18,
995 ),
996 Unit::new_linear(
997 UnitCategory::InformationStorage,
998 &["zettabits", "zbit", "zb"],
999 1e21,
1000 ),
1001 Unit::new_linear(
1002 UnitCategory::InformationStorage,
1003 &["yottabits", "ybit", "yb"],
1004 1e24,
1005 ),
1006 Unit::new_linear(
1007 UnitCategory::InformationStorage,
1008 &["kilobytes", "kbyte", "kB"],
1009 8000.0,
1010 ),
1011 Unit::new_linear(
1012 UnitCategory::InformationStorage,
1013 &["megabytes", "mbyte", "MB"],
1014 8e6,
1015 ),
1016 Unit::new_linear(
1017 UnitCategory::InformationStorage,
1018 &["gigabytes", "gbyte", "GB"],
1019 8e9,
1020 ),
1021 Unit::new_linear(
1022 UnitCategory::InformationStorage,
1023 &["terabytes", "tbyte", "TB"],
1024 8e12,
1025 ),
1026 Unit::new_linear(
1027 UnitCategory::InformationStorage,
1028 &["petabytes", "pbyte", "PB"],
1029 8e15,
1030 ),
1031 Unit::new_linear(
1032 UnitCategory::InformationStorage,
1033 &["exabytes", "ebyte", "EB"],
1034 8e18,
1035 ),
1036 Unit::new_linear(
1037 UnitCategory::InformationStorage,
1038 &["zettabytes", "zbyte", "ZB"],
1039 8e21,
1040 ),
1041 Unit::new_linear(
1042 UnitCategory::InformationStorage,
1043 &["yottabytes", "ybyte", "YB"],
1044 8e24,
1045 ),
1046 Unit::new_linear(
1047 UnitCategory::InformationStorage,
1048 &["kibibits", "kibit", "kib"],
1049 1024.0,
1050 ),
1051 Unit::new_linear(
1052 UnitCategory::InformationStorage,
1053 &["mebibits", "mibit", "mib"],
1054 1048576.0,
1055 ),
1056 Unit::new_linear(
1057 UnitCategory::InformationStorage,
1058 &["gibibits", "gibit", "gib"],
1059 1073741824.0,
1060 ),
1061 Unit::new_linear(
1062 UnitCategory::InformationStorage,
1063 &["tebibits", "tibit", "tib"],
1064 1099511627776.0,
1065 ),
1066 Unit::new_linear(
1067 UnitCategory::InformationStorage,
1068 &["pebibits", "pibit", "pib"],
1069 1125899906842624.0,
1070 ),
1071 Unit::new_linear(
1072 UnitCategory::InformationStorage,
1073 &["exbibits", "eibit", "eib"],
1074 1152921504606846976.0,
1075 ),
1076 Unit::new_linear(
1077 UnitCategory::InformationStorage,
1078 &["zebibits", "zibit", "zib"],
1079 1180591620717411303424.0,
1080 ),
1081 Unit::new_linear(
1082 UnitCategory::InformationStorage,
1083 &["yobibits", "yibit", "yib"],
1084 1208925819614629174706176.0,
1085 ),
1086 Unit::new_linear(
1087 UnitCategory::InformationStorage,
1088 &["kibibytes", "kibyte", "KiB"],
1089 8192.0,
1090 ),
1091 Unit::new_linear(
1092 UnitCategory::InformationStorage,
1093 &["mebibytes", "mibyte", "MiB"],
1094 8388608.0,
1095 ),
1096 Unit::new_linear(
1097 UnitCategory::InformationStorage,
1098 &["gibibytes", "gibyte", "GiB"],
1099 8589934592.0,
1100 ),
1101 Unit::new_linear(
1102 UnitCategory::InformationStorage,
1103 &["tebibytes", "tibyte", "TiB"],
1104 8796093022208.0,
1105 ),
1106 Unit::new_linear(
1107 UnitCategory::InformationStorage,
1108 &["pebibytes", "pibyte", "PiB"],
1109 9007199254740992.0,
1110 ),
1111 Unit::new_linear(
1112 UnitCategory::InformationStorage,
1113 &["exbibytes", "eibyte", "EiB"],
1114 9223372036854775808.0,
1115 ),
1116 Unit::new_linear(
1117 UnitCategory::InformationStorage,
1118 &["zebibytes", "zibyte", "ZiB"],
1119 9444732965739290427392.0,
1120 ),
1121 Unit::new_linear(
1122 UnitCategory::InformationStorage,
1123 &["yobibytes", "yibyte", "YiB"],
1124 9671406556917033397649408.0,
1125 ),
1126 ]
1127}
1128
1129pub fn resolve_unit(identifier: &str) -> Result<Unit> {
1130 let units = get_all_units();
1131 let identifier_lower = identifier.to_lowercase();
1132
1133 let mut exact_matches: Vec<Unit> = Vec::new();
1134 let mut case_matches: Vec<Unit> = Vec::new();
1135
1136 for unit in units {
1137 let is_exact = unit.matches_exact(identifier);
1138 let is_case = unit
1139 .identifiers
1140 .iter()
1141 .any(|alias| alias.to_lowercase() == identifier_lower);
1142
1143 if is_exact {
1144 exact_matches.push(unit.clone());
1145 }
1146
1147 if is_case {
1148 case_matches.push(unit);
1149 }
1150 }
1151
1152 if exact_matches.len() == 1 {
1153 return Ok(exact_matches.remove(0));
1154 }
1155
1156 if exact_matches.len() > 1 {
1157 let mut suggestions = exact_matches
1158 .iter()
1159 .map(|unit| format!("{} ({})", identifier, unit.category.name()))
1160 .collect::<Vec<_>>();
1161 suggestions.sort();
1162 suggestions.dedup();
1163 return Err(anyhow!(
1164 "Ambiguous unit '{}'; try a more specific name such as {}",
1165 identifier,
1166 suggestions.join(", ")
1167 ));
1168 }
1169
1170 if case_matches.is_empty() {
1171 return Err(anyhow!("Unknown unit: {}", identifier));
1172 }
1173
1174 if case_matches.len() == 1 {
1175 return Ok(case_matches.remove(0));
1176 }
1177
1178 let mut suggestions = case_matches
1179 .iter()
1180 .map(|unit| {
1181 let display = unit
1182 .identifiers
1183 .iter()
1184 .find(|alias| alias.to_lowercase() == identifier_lower)
1185 .copied()
1186 .unwrap_or(unit.identifiers[0]);
1187 format!("{} ({})", display, unit.category.name())
1188 })
1189 .collect::<Vec<_>>();
1190 suggestions.sort();
1191 suggestions.dedup();
1192
1193 Err(anyhow!(
1194 "Ambiguous unit '{}'; try using specific casing such as {}",
1195 identifier,
1196 suggestions.join(", ")
1197 ))
1198}
1199
1200pub fn find_unit(identifier: &str) -> Option<Unit> {
1201 resolve_unit(identifier).ok()
1202}
1203
1204pub fn convert(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
1205 let from = resolve_unit(from_unit)?;
1206 let to = resolve_unit(to_unit)?;
1207
1208 if from.category != to.category {
1209 return Err(anyhow!(
1210 "Cannot convert {} to {}",
1211 from.category.name(),
1212 to.category.name()
1213 ));
1214 }
1215
1216 let base_value = from.convert_to_base(value);
1218 let result = to.convert_from_base(base_value);
1219
1220 Ok(result)
1221}
1222
1223#[cfg(test)]
1224mod tests {
1225 use super::*;
1226
1227 #[test]
1228 fn test_basic_length_conversion() {
1229 let result = convert(1.0, "km", "m").unwrap();
1230 assert!((result - 1000.0).abs() < 1e-10);
1231 }
1232
1233 #[test]
1234 fn test_temperature_conversion() {
1235 let result = convert(0.0, "celsius", "fahrenheit").unwrap();
1236 assert!((result - 32.0).abs() < 1e-10);
1237
1238 let result = convert(100.0, "celsius", "fahrenheit").unwrap();
1239 assert!((result - 212.0).abs() < 1e-10);
1240 }
1241
1242 #[test]
1243 fn test_same_unit_conversion() {
1244 let result = convert(42.0, "meters", "m").unwrap();
1245 assert!((result - 42.0).abs() < 1e-10);
1246 }
1247
1248 #[test]
1249 fn test_incompatible_units() {
1250 let result = convert(1.0, "kg", "meters");
1251 assert!(result.is_err());
1252 assert!(result.unwrap_err().to_string().contains("Cannot convert"));
1253 }
1254
1255 #[test]
1256 fn test_unknown_unit() {
1257 let result = convert(1.0, "foobar", "meters");
1258 assert!(result.is_err());
1259 assert!(result.unwrap_err().to_string().contains("Unknown unit"));
1260 }
1261
1262 #[test]
1263 fn test_ambiguous_unit_identifier() {
1264 let result = convert(1.0, "ma", "amperes");
1265 assert!(result.is_err());
1266 assert!(result.unwrap_err().to_string().contains("Ambiguous unit"));
1267 }
1268
1269 #[test]
1270 fn test_information_storage() {
1271 let result = convert(1.0, "kibibytes", "bytes").unwrap();
1272 assert!((result - 1024.0).abs() < 1e-10);
1273 }
1274
1275 #[test]
1276 fn test_case_insensitive() {
1277 let result = convert(1.0, "KM", "M").unwrap();
1278 assert!((result - 1000.0).abs() < 1e-10);
1279 }
1280
1281 #[test]
1282 fn test_angle_conversion() {
1283 let result = convert(180.0, "degrees", "radians").unwrap();
1284 assert!((result - std::f64::consts::PI).abs() < 1e-10);
1285 }
1286
1287 #[test]
1288 fn test_area_conversion() {
1289 let result = convert(1.0, "square kilometers", "square meters").unwrap();
1290 assert!((result - 1e6).abs() < 1e-6);
1291 }
1292
1293 #[test]
1294 fn test_concentration_mass_conversion() {
1295 let result = convert(1.0, "grams per liter", "milligrams per deciliter").unwrap();
1296 assert!((result - 100.0).abs() < 1e-10);
1297 }
1298
1299 #[test]
1300 fn test_duration_conversion() {
1301 let result = convert(1.0, "hours", "seconds").unwrap();
1302 assert!((result - 3600.0).abs() < 1e-10);
1303 }
1304
1305 #[test]
1306 fn test_electric_charge_conversion() {
1307 let result = convert(1.0, "ampere hours", "coulombs").unwrap();
1308 assert!((result - 3600.0).abs() < 1e-10);
1309 }
1310
1311 #[test]
1312 fn test_electric_current_conversion() {
1313 let result = convert(1.0, "kiloamperes", "amperes").unwrap();
1314 assert!((result - 1000.0).abs() < 1e-10);
1315 }
1316
1317 #[test]
1318 fn test_electric_potential_difference_conversion() {
1319 let result = convert(1.0, "kilovolts", "volts").unwrap();
1320 assert!((result - 1000.0).abs() < 1e-10);
1321 }
1322
1323 #[test]
1324 fn test_electric_resistance_conversion() {
1325 let result = convert(1.0, "kiloohms", "ohms").unwrap();
1326 assert!((result - 1000.0).abs() < 1e-10);
1327 }
1328
1329 #[test]
1330 fn test_energy_conversion() {
1331 let result = convert(1.0, "kilocalories", "calories").unwrap();
1332 assert!((result - 1000.0).abs() < 1e-10);
1333 }
1334
1335 #[test]
1336 fn test_frequency_conversion() {
1337 let result = convert(1.0, "megahertz", "hertz").unwrap();
1338 assert!((result - 1e6).abs() < 1e-6);
1339 }
1340
1341 #[test]
1342 fn test_fuel_efficiency_conversion() {
1343 let result = convert(10.0, "liters per 100 kilometers", "miles per gallon").unwrap();
1344 assert!((result - 23.521_458_333_333_332).abs() < 1e-9);
1345
1346 let result = convert(7.5, "miles per gallon", "liters per 100 kilometers").unwrap();
1347 assert!((result - (235.214_583_333_333_34 / 7.5)).abs() < 1e-9);
1348
1349 let result = convert(40.0, "miles per gallon", "miles per imperial gallon").unwrap();
1350 assert!((result - 48.037_997_020_194_2).abs() < 1e-9);
1351
1352 let result = convert(
1353 50.0,
1354 "miles per imperial gallon",
1355 "liters per 100 kilometers",
1356 )
1357 .unwrap();
1358 assert!((result - 5.649_618_726_636_444).abs() < 1e-9);
1359 }
1360
1361 #[test]
1362 fn test_power_conversion() {
1363 let result = convert(1.0, "kilowatts", "watts").unwrap();
1364 assert!((result - 1000.0).abs() < 1e-10);
1365 }
1366
1367 #[test]
1368 fn test_pressure_conversion() {
1369 let result = convert(1.0, "bars", "pascals").unwrap();
1370 assert!((result - 100000.0).abs() < 1e-6);
1371 }
1372
1373 #[test]
1374 fn test_speed_conversion() {
1375 let result = convert(1.0, "kilometers per hour", "meters per second").unwrap();
1376 assert!((result - (1.0 / 3.6)).abs() < 1e-10);
1377 }
1378
1379 #[test]
1380 fn test_volume_conversion() {
1381 let result = convert(1.0, "liters", "milliliters").unwrap();
1382 assert!((result - 1000.0).abs() < 1e-10);
1383 }
1384}