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.contains(&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(
436 UnitCategory::Area,
437 &["acres", "acre", "ac"],
438 4_046.856_422_4,
439 ),
440 Unit::new_linear(UnitCategory::Area, &["ares", "are", "a"], 100.0),
441 Unit::new_linear(UnitCategory::Area, &["hectares", "hectare", "ha"], 10000.0),
442 Unit::new_linear(
443 UnitCategory::Area,
444 &["square megameters", "square megameter", "Mm²", "Mm2"],
445 1e12,
446 ),
447 Unit::new_linear(
449 UnitCategory::Volume,
450 &["liters", "l", "liter", "litre", "litres"],
451 1.0,
452 ),
453 Unit::new_linear(
454 UnitCategory::Volume,
455 &[
456 "milliliters",
457 "ml",
458 "milliliter",
459 "millilitre",
460 "millilitres",
461 ],
462 0.001,
463 ),
464 Unit::new_linear(
465 UnitCategory::Volume,
466 &[
467 "centiliters",
468 "cl",
469 "centiliter",
470 "centilitre",
471 "centilitres",
472 ],
473 0.01,
474 ),
475 Unit::new_linear(
476 UnitCategory::Volume,
477 &["deciliters", "dl", "deciliter", "decilitre", "decilitres"],
478 0.1,
479 ),
480 Unit::new_linear(
481 UnitCategory::Volume,
482 &["kiloliters", "kl", "kiloliter", "kilolitre", "kilolitres"],
483 1000.0,
484 ),
485 Unit::new_linear(
486 UnitCategory::Volume,
487 &["megaliter", "megaliters", "ML"],
488 1e6,
489 ),
490 Unit::new_linear(
491 UnitCategory::Volume,
492 &[
493 "cubic meters",
494 "cubic meter",
495 "m³",
496 "m3",
497 "cubic metres",
498 "cubic metre",
499 ],
500 1000.0,
501 ),
502 Unit::new_linear(
503 UnitCategory::Volume,
504 &[
505 "cubic kilometers",
506 "cubic kilometer",
507 "km³",
508 "km3",
509 "cubic kilometres",
510 "cubic kilometre",
511 ],
512 1e12,
513 ),
514 Unit::new_linear(
515 UnitCategory::Volume,
516 &[
517 "cubic centimeters",
518 "cubic centimeter",
519 "cm³",
520 "cm3",
521 "cc",
522 "cubic centimetres",
523 "cubic centimetre",
524 ],
525 0.001,
526 ),
527 Unit::new_linear(
528 UnitCategory::Volume,
529 &[
530 "cubic millimeters",
531 "cubic millimeter",
532 "mm³",
533 "mm3",
534 "cubic millimetres",
535 "cubic millimetre",
536 ],
537 1e-6,
538 ),
539 Unit::new_linear(
540 UnitCategory::Volume,
541 &["cubic inches", "in³", "in3", "cubic inch"],
542 0.016387064,
543 ),
544 Unit::new_linear(
545 UnitCategory::Volume,
546 &["cubic feet", "ft³", "ft3", "cubic foot"],
547 28.316846592,
548 ),
549 Unit::new_linear(
550 UnitCategory::Volume,
551 &["cubic yards", "yd³", "yd3", "cubic yard"],
552 764.554857984,
553 ),
554 Unit::new_linear(
555 UnitCategory::Volume,
556 &["cubic miles", "mi³", "mi3", "cubic mile"],
557 4.168_181_825_440_58e12,
558 ),
559 Unit::new_linear(
560 UnitCategory::Volume,
561 &["acre feet", "acre foot", "acre ft"],
562 1_233_481.837_547_52,
563 ),
564 Unit::new_linear(
565 UnitCategory::Volume,
566 &["bushels", "bushel", "bsh"],
567 35.23907016688,
568 ),
569 Unit::new_linear(
570 UnitCategory::Volume,
571 &["teaspoons", "tsp", "teaspoon"],
572 0.00492892159375,
573 ),
574 Unit::new_linear(
575 UnitCategory::Volume,
576 &["tablespoons", "tbsp", "tablespoon"],
577 0.01478676478125,
578 ),
579 Unit::new_linear(
580 UnitCategory::Volume,
581 &["fluid ounces", "fl oz", "fluid ounce"],
582 0.0295735295625,
583 ),
584 Unit::new_linear(UnitCategory::Volume, &["cups", "cup"], 0.236_588_236_5),
585 Unit::new_linear(UnitCategory::Volume, &["pints", "pt", "pint"], 0.473176473),
586 Unit::new_linear(
587 UnitCategory::Volume,
588 &["quarts", "qt", "quart"],
589 0.946352946,
590 ),
591 Unit::new_linear(
592 UnitCategory::Volume,
593 &["gallons", "gal", "gallon"],
594 3.785411784,
595 ),
596 Unit::new_linear(
597 UnitCategory::Volume,
598 &["imperial teaspoons", "imperial teaspoon", "imp tsp"],
599 0.00591938802083,
600 ),
601 Unit::new_linear(
602 UnitCategory::Volume,
603 &["imperial tablespoons", "imperial tablespoon", "imp tbsp"],
604 0.0177581640625,
605 ),
606 Unit::new_linear(
607 UnitCategory::Volume,
608 &["imperial fluid ounces", "imperial fluid ounce", "imp fl oz"],
609 0.0284130625,
610 ),
611 Unit::new_linear(
612 UnitCategory::Volume,
613 &["imperial pints", "imperial pint", "imp pt"],
614 0.56826125,
615 ),
616 Unit::new_linear(
617 UnitCategory::Volume,
618 &["imperial quarts", "imperial quart", "imp qt"],
619 1.1365225,
620 ),
621 Unit::new_linear(
622 UnitCategory::Volume,
623 &["imperial gallons", "imperial gallon", "imp gal"],
624 4.54609,
625 ),
626 Unit::new_linear(UnitCategory::Volume, &["metric cups", "metric cup"], 0.25),
627 Unit::new_linear(UnitCategory::Angle, &["degrees", "deg", "°", "degree"], 1.0),
629 Unit::new_linear(
630 UnitCategory::Angle,
631 &["arc minutes", "arcminutes", "arcmin", "′", "arc minute"],
632 1.0 / 60.0,
633 ),
634 Unit::new_linear(
635 UnitCategory::Angle,
636 &["arc seconds", "arcseconds", "arcsec", "″", "arc second"],
637 1.0 / 3600.0,
638 ),
639 Unit::new_linear(
640 UnitCategory::Angle,
641 &["radians", "rad", "radian"],
642 180.0 / std::f64::consts::PI,
643 ),
644 Unit::new_linear(
645 UnitCategory::Angle,
646 &["gradians", "grad", "gradian", "gon"],
647 0.9,
648 ),
649 Unit::new_linear(
650 UnitCategory::Angle,
651 &["revolutions", "rev", "revolution", "rotation"],
652 360.0,
653 ),
654 Unit::new_linear(
656 UnitCategory::Duration,
657 &["seconds", "s", "sec", "second"],
658 1.0,
659 ),
660 Unit::new_linear(
661 UnitCategory::Duration,
662 &["milliseconds", "ms", "millisecond"],
663 0.001,
664 ),
665 Unit::new_linear(
666 UnitCategory::Duration,
667 &["microseconds", "μs", "microsecond", "us"],
668 1e-6,
669 ),
670 Unit::new_linear(
671 UnitCategory::Duration,
672 &["nanoseconds", "ns", "nanosecond"],
673 1e-9,
674 ),
675 Unit::new_linear(
676 UnitCategory::Duration,
677 &["picoseconds", "ps", "picosecond"],
678 1e-12,
679 ),
680 Unit::new_linear(UnitCategory::Duration, &["minutes", "min", "minute"], 60.0),
681 Unit::new_linear(
682 UnitCategory::Duration,
683 &["hours", "h", "hr", "hour"],
684 3600.0,
685 ),
686 Unit::new_linear(UnitCategory::Duration, &["days", "d", "day"], 86400.0),
687 Unit::new_linear(UnitCategory::Duration, &["weeks", "wk", "week"], 604800.0),
688 Unit::new_linear(
690 UnitCategory::Speed,
691 &["meters per second", "m/s", "mps"],
692 1.0,
693 ),
694 Unit::new_linear(
695 UnitCategory::Speed,
696 &["kilometers per hour", "km/h", "kph", "kmph"],
697 1.0 / 3.6,
698 ),
699 Unit::new_linear(
700 UnitCategory::Speed,
701 &["miles per hour", "mph", "mi/h"],
702 0.44704,
703 ),
704 Unit::new_linear(
705 UnitCategory::Speed,
706 &["knots", "knot", "kn"],
707 0.514_444_444_444_444_5,
708 ),
709 Unit::new_linear(UnitCategory::Energy, &["joules", "j", "joule"], 1.0),
711 Unit::new_linear(
712 UnitCategory::Energy,
713 &["kilojoules", "kj", "kilojoule"],
714 1000.0,
715 ),
716 Unit::new_linear(UnitCategory::Energy, &["calories", "cal", "calorie"], 4.184),
717 Unit::new_linear(
718 UnitCategory::Energy,
719 &["kilocalories", "kcal", "kilocalorie", "food calorie", "Cal"],
720 4184.0,
721 ),
722 Unit::new_linear(
723 UnitCategory::Energy,
724 &["watt hours", "wh", "watt hour"],
725 3600.0,
726 ),
727 Unit::new_linear(
728 UnitCategory::Energy,
729 &["kilowatt hours", "kwh", "kilowatt hour"],
730 3.6e6,
731 ),
732 Unit::new_linear(
733 UnitCategory::Energy,
734 &["electronvolts", "ev", "electronvolt"],
735 1.602176634e-19,
736 ),
737 Unit::new_linear(UnitCategory::Power, &["watts", "w", "watt"], 1.0),
739 Unit::new_linear(
740 UnitCategory::Power,
741 &["milliwatts", "mw", "milliwatt"],
742 0.001,
743 ),
744 Unit::new_linear(
745 UnitCategory::Power,
746 &["microwatts", "μw", "microwatt", "uw"],
747 1e-6,
748 ),
749 Unit::new_linear(UnitCategory::Power, &["nanowatts", "nw", "nanowatt"], 1e-9),
750 Unit::new_linear(UnitCategory::Power, &["picowatts", "pw", "picowatt"], 1e-12),
751 Unit::new_linear(
752 UnitCategory::Power,
753 &["femtowatts", "fw", "femtowatt"],
754 1e-15,
755 ),
756 Unit::new_linear(
757 UnitCategory::Power,
758 &["kilowatts", "kw", "kilowatt"],
759 1000.0,
760 ),
761 Unit::new_linear(UnitCategory::Power, &["megawatts", "MW", "megawatt"], 1e6),
762 Unit::new_linear(UnitCategory::Power, &["gigawatts", "GW", "gigawatt"], 1e9),
763 Unit::new_linear(UnitCategory::Power, &["terawatts", "TW", "terawatt"], 1e12),
764 Unit::new_linear(
765 UnitCategory::Power,
766 &["horsepower", "hp"],
767 745.699_871_582_270_1,
768 ),
769 Unit::new_linear(UnitCategory::Pressure, &["pascals", "pa", "pascal"], 1.0),
771 Unit::new_linear(
772 UnitCategory::Pressure,
773 &["hectopascals", "hpa", "hectopascal"],
774 100.0,
775 ),
776 Unit::new_linear(
777 UnitCategory::Pressure,
778 &["kilopascals", "kpa", "kilopascal"],
779 1000.0,
780 ),
781 Unit::new_linear(
782 UnitCategory::Pressure,
783 &["megapascals", "mpa", "megapascal"],
784 1e6,
785 ),
786 Unit::new_linear(
787 UnitCategory::Pressure,
788 &["gigapascals", "gpa", "gigapascal"],
789 1e9,
790 ),
791 Unit::new_linear(UnitCategory::Pressure, &["bars", "bar"], 100000.0),
792 Unit::new_linear(
793 UnitCategory::Pressure,
794 &["millibars", "mbar", "millibar"],
795 100.0,
796 ),
797 Unit::new_linear(
798 UnitCategory::Pressure,
799 &["millimeters of mercury", "mmhg", "mm hg"],
800 133.322387415,
801 ),
802 Unit::new_linear(
803 UnitCategory::Pressure,
804 &["inches of mercury", "inhg", "in hg"],
805 3386.389,
806 ),
807 Unit::new_linear(
808 UnitCategory::Pressure,
809 &["pounds per square inch", "psi", "lbf/in²"],
810 6894.757293168,
811 ),
812 Unit::new_linear(
813 UnitCategory::Pressure,
814 &["atmospheres", "atm", "atmosphere"],
815 101325.0,
816 ),
817 Unit::new_linear(UnitCategory::Frequency, &["hertz", "hz", "Hz"], 1.0),
819 Unit::new_linear(UnitCategory::Frequency, &["millihertz", "mHz"], 0.001),
820 Unit::new_linear(UnitCategory::Frequency, &["microhertz", "µHz", "uHz"], 1e-6),
821 Unit::new_linear(UnitCategory::Frequency, &["nanohertz", "nHz"], 1e-9),
822 Unit::new_linear(UnitCategory::Frequency, &["kilohertz", "kHz"], 1000.0),
823 Unit::new_linear(UnitCategory::Frequency, &["megahertz", "MHz"], 1e6),
824 Unit::new_linear(UnitCategory::Frequency, &["gigahertz", "GHz"], 1e9),
825 Unit::new_linear(UnitCategory::Frequency, &["terahertz", "THz"], 1e12),
826 Unit::new_linear(
828 UnitCategory::ElectricCharge,
829 &["coulombs", "c", "coulomb"],
830 1.0,
831 ),
832 Unit::new_linear(
833 UnitCategory::ElectricCharge,
834 &["megaampere hours", "MAh"],
835 3.6e9,
836 ),
837 Unit::new_linear(
838 UnitCategory::ElectricCharge,
839 &["kiloampere hours", "kAh"],
840 3.6e6,
841 ),
842 Unit::new_linear(
843 UnitCategory::ElectricCharge,
844 &["ampere hours", "Ah"],
845 3600.0,
846 ),
847 Unit::new_linear(
848 UnitCategory::ElectricCharge,
849 &["milliampere hours", "mAh"],
850 3.6,
851 ),
852 Unit::new_linear(
853 UnitCategory::ElectricCharge,
854 &["microampere hours", "µAh", "uAh"],
855 0.0036,
856 ),
857 Unit::new_linear(
859 UnitCategory::ElectricCurrent,
860 &["amperes", "A", "amp", "amps", "ampere"],
861 1.0,
862 ),
863 Unit::new_linear(UnitCategory::ElectricCurrent, &["megaamperes", "MA"], 1e6),
864 Unit::new_linear(
865 UnitCategory::ElectricCurrent,
866 &["kiloamperes", "kA"],
867 1000.0,
868 ),
869 Unit::new_linear(
870 UnitCategory::ElectricCurrent,
871 &["milliamperes", "mA"],
872 0.001,
873 ),
874 Unit::new_linear(
875 UnitCategory::ElectricCurrent,
876 &["microamperes", "µA", "uA"],
877 1e-6,
878 ),
879 Unit::new_linear(
881 UnitCategory::ElectricPotentialDifference,
882 &["volts", "V", "volt", "v"],
883 1.0,
884 ),
885 Unit::new_linear(
886 UnitCategory::ElectricPotentialDifference,
887 &["megavolts", "MV"],
888 1e6,
889 ),
890 Unit::new_linear(
891 UnitCategory::ElectricPotentialDifference,
892 &["kilovolts", "kV"],
893 1000.0,
894 ),
895 Unit::new_linear(
896 UnitCategory::ElectricPotentialDifference,
897 &["millivolts", "mV"],
898 0.001,
899 ),
900 Unit::new_linear(
901 UnitCategory::ElectricPotentialDifference,
902 &["microvolts", "µV", "uV"],
903 1e-6,
904 ),
905 Unit::new_linear(
907 UnitCategory::ElectricResistance,
908 &["ohms", "Ω", "ohm", "ω"],
909 1.0,
910 ),
911 Unit::new_linear(
912 UnitCategory::ElectricResistance,
913 &["megaohms", "MΩ", "megohm"],
914 1e6,
915 ),
916 Unit::new_linear(
917 UnitCategory::ElectricResistance,
918 &["kiloohms", "kΩ", "kilohm"],
919 1000.0,
920 ),
921 Unit::new_linear(
922 UnitCategory::ElectricResistance,
923 &["milliohms", "mΩ", "milliohm"],
924 0.001,
925 ),
926 Unit::new_linear(
927 UnitCategory::ElectricResistance,
928 &["microohms", "µΩ", "microhm", "uΩ"],
929 1e-6,
930 ),
931 Unit::new_linear(
933 UnitCategory::ConcentrationMass,
934 &["grams per liter", "g/l"],
935 1.0,
936 ),
937 Unit::new_linear(
938 UnitCategory::ConcentrationMass,
939 &["milligrams per deciliter", "mg/dl"],
940 0.01,
941 ),
942 Unit::new_linear(
944 UnitCategory::FuelEfficiency,
945 &["liters per 100 kilometers", "l/100km"],
946 1.0,
947 ),
948 Unit::new_reciprocal(
949 UnitCategory::FuelEfficiency,
950 &["miles per gallon", "mpg"],
951 235.214_583_333_333_34,
952 ),
953 Unit::new_reciprocal(
954 UnitCategory::FuelEfficiency,
955 &["miles per imperial gallon", "imp mpg"],
956 282.480_936_331_822_2,
957 ),
958 Unit::new_linear(UnitCategory::InformationStorage, &["bits", "bit", "b"], 1.0),
960 Unit::new_linear(
961 UnitCategory::InformationStorage,
962 &["nibbles", "nibble"],
963 4.0,
964 ),
965 Unit::new_linear(
966 UnitCategory::InformationStorage,
967 &["bytes", "byte", "B"],
968 8.0,
969 ),
970 Unit::new_linear(
971 UnitCategory::InformationStorage,
972 &["kilobits", "kbit", "kb"],
973 1000.0,
974 ),
975 Unit::new_linear(
976 UnitCategory::InformationStorage,
977 &["megabits", "mbit", "mb"],
978 1e6,
979 ),
980 Unit::new_linear(
981 UnitCategory::InformationStorage,
982 &["gigabits", "gbit", "gb"],
983 1e9,
984 ),
985 Unit::new_linear(
986 UnitCategory::InformationStorage,
987 &["terabits", "tbit", "tb"],
988 1e12,
989 ),
990 Unit::new_linear(
991 UnitCategory::InformationStorage,
992 &["petabits", "pbit", "pb"],
993 1e15,
994 ),
995 Unit::new_linear(
996 UnitCategory::InformationStorage,
997 &["exabits", "ebit", "eb"],
998 1e18,
999 ),
1000 Unit::new_linear(
1001 UnitCategory::InformationStorage,
1002 &["zettabits", "zbit", "zb"],
1003 1e21,
1004 ),
1005 Unit::new_linear(
1006 UnitCategory::InformationStorage,
1007 &["yottabits", "ybit", "yb"],
1008 1e24,
1009 ),
1010 Unit::new_linear(
1011 UnitCategory::InformationStorage,
1012 &["kilobytes", "kbyte", "kB"],
1013 8000.0,
1014 ),
1015 Unit::new_linear(
1016 UnitCategory::InformationStorage,
1017 &["megabytes", "mbyte", "MB"],
1018 8e6,
1019 ),
1020 Unit::new_linear(
1021 UnitCategory::InformationStorage,
1022 &["gigabytes", "gbyte", "GB"],
1023 8e9,
1024 ),
1025 Unit::new_linear(
1026 UnitCategory::InformationStorage,
1027 &["terabytes", "tbyte", "TB"],
1028 8e12,
1029 ),
1030 Unit::new_linear(
1031 UnitCategory::InformationStorage,
1032 &["petabytes", "pbyte", "PB"],
1033 8e15,
1034 ),
1035 Unit::new_linear(
1036 UnitCategory::InformationStorage,
1037 &["exabytes", "ebyte", "EB"],
1038 8e18,
1039 ),
1040 Unit::new_linear(
1041 UnitCategory::InformationStorage,
1042 &["zettabytes", "zbyte", "ZB"],
1043 8e21,
1044 ),
1045 Unit::new_linear(
1046 UnitCategory::InformationStorage,
1047 &["yottabytes", "ybyte", "YB"],
1048 8e24,
1049 ),
1050 Unit::new_linear(
1051 UnitCategory::InformationStorage,
1052 &["kibibits", "kibit", "kib"],
1053 1024.0,
1054 ),
1055 Unit::new_linear(
1056 UnitCategory::InformationStorage,
1057 &["mebibits", "mibit", "mib"],
1058 1048576.0,
1059 ),
1060 Unit::new_linear(
1061 UnitCategory::InformationStorage,
1062 &["gibibits", "gibit", "gib"],
1063 1073741824.0,
1064 ),
1065 Unit::new_linear(
1066 UnitCategory::InformationStorage,
1067 &["tebibits", "tibit", "tib"],
1068 1099511627776.0,
1069 ),
1070 Unit::new_linear(
1071 UnitCategory::InformationStorage,
1072 &["pebibits", "pibit", "pib"],
1073 1125899906842624.0,
1074 ),
1075 Unit::new_linear(
1076 UnitCategory::InformationStorage,
1077 &["exbibits", "eibit", "eib"],
1078 1152921504606846976.0,
1079 ),
1080 Unit::new_linear(
1081 UnitCategory::InformationStorage,
1082 &["zebibits", "zibit", "zib"],
1083 1180591620717411303424.0,
1084 ),
1085 Unit::new_linear(
1086 UnitCategory::InformationStorage,
1087 &["yobibits", "yibit", "yib"],
1088 1208925819614629174706176.0,
1089 ),
1090 Unit::new_linear(
1091 UnitCategory::InformationStorage,
1092 &["kibibytes", "kibyte", "KiB"],
1093 8192.0,
1094 ),
1095 Unit::new_linear(
1096 UnitCategory::InformationStorage,
1097 &["mebibytes", "mibyte", "MiB"],
1098 8388608.0,
1099 ),
1100 Unit::new_linear(
1101 UnitCategory::InformationStorage,
1102 &["gibibytes", "gibyte", "GiB"],
1103 8589934592.0,
1104 ),
1105 Unit::new_linear(
1106 UnitCategory::InformationStorage,
1107 &["tebibytes", "tibyte", "TiB"],
1108 8796093022208.0,
1109 ),
1110 Unit::new_linear(
1111 UnitCategory::InformationStorage,
1112 &["pebibytes", "pibyte", "PiB"],
1113 9007199254740992.0,
1114 ),
1115 Unit::new_linear(
1116 UnitCategory::InformationStorage,
1117 &["exbibytes", "eibyte", "EiB"],
1118 9223372036854775808.0,
1119 ),
1120 Unit::new_linear(
1121 UnitCategory::InformationStorage,
1122 &["zebibytes", "zibyte", "ZiB"],
1123 9444732965739290427392.0,
1124 ),
1125 Unit::new_linear(
1126 UnitCategory::InformationStorage,
1127 &["yobibytes", "yibyte", "YiB"],
1128 9671406556917033397649408.0,
1129 ),
1130 ]
1131}
1132
1133pub fn resolve_unit(identifier: &str) -> Result<Unit> {
1134 let units = get_all_units();
1135 let identifier_lower = identifier.to_lowercase();
1136
1137 let mut exact_matches: Vec<Unit> = Vec::new();
1138 let mut case_matches: Vec<Unit> = Vec::new();
1139
1140 for unit in units {
1141 let is_exact = unit.matches_exact(identifier);
1142 let is_case = unit
1143 .identifiers
1144 .iter()
1145 .any(|alias| alias.to_lowercase() == identifier_lower);
1146
1147 if is_exact {
1148 exact_matches.push(unit.clone());
1149 }
1150
1151 if is_case {
1152 case_matches.push(unit);
1153 }
1154 }
1155
1156 if exact_matches.len() == 1 {
1157 return Ok(exact_matches.remove(0));
1158 }
1159
1160 if exact_matches.len() > 1 {
1161 let mut suggestions = exact_matches
1162 .iter()
1163 .map(|unit| format!("{} ({})", identifier, unit.category.name()))
1164 .collect::<Vec<_>>();
1165 suggestions.sort();
1166 suggestions.dedup();
1167 return Err(anyhow!(
1168 "Ambiguous unit '{}'; try a more specific name such as {}",
1169 identifier,
1170 suggestions.join(", ")
1171 ));
1172 }
1173
1174 if case_matches.is_empty() {
1175 return Err(anyhow!("Unknown unit: {}", identifier));
1176 }
1177
1178 if case_matches.len() == 1 {
1179 return Ok(case_matches.remove(0));
1180 }
1181
1182 let mut suggestions = case_matches
1183 .iter()
1184 .map(|unit| {
1185 let display = unit
1186 .identifiers
1187 .iter()
1188 .find(|alias| alias.to_lowercase() == identifier_lower)
1189 .copied()
1190 .unwrap_or(unit.identifiers[0]);
1191 format!("{} ({})", display, unit.category.name())
1192 })
1193 .collect::<Vec<_>>();
1194 suggestions.sort();
1195 suggestions.dedup();
1196
1197 Err(anyhow!(
1198 "Ambiguous unit '{}'; try using specific casing such as {}",
1199 identifier,
1200 suggestions.join(", ")
1201 ))
1202}
1203
1204pub fn find_unit(identifier: &str) -> Option<Unit> {
1205 resolve_unit(identifier).ok()
1206}
1207
1208pub fn convert(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
1209 let from = resolve_unit(from_unit)?;
1210 let to = resolve_unit(to_unit)?;
1211
1212 if from.category != to.category {
1213 return Err(anyhow!(
1214 "Cannot convert {} to {}",
1215 from.category.name(),
1216 to.category.name()
1217 ));
1218 }
1219
1220 let base_value = from.convert_to_base(value);
1222 let result = to.convert_from_base(base_value);
1223
1224 Ok(result)
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229 use super::*;
1230
1231 #[test]
1232 fn test_basic_length_conversion() {
1233 let result = convert(1.0, "km", "m").unwrap();
1234 assert!((result - 1000.0).abs() < 1e-10);
1235 }
1236
1237 #[test]
1238 fn test_temperature_conversion() {
1239 let result = convert(0.0, "celsius", "fahrenheit").unwrap();
1240 assert!((result - 32.0).abs() < 1e-10);
1241
1242 let result = convert(100.0, "celsius", "fahrenheit").unwrap();
1243 assert!((result - 212.0).abs() < 1e-10);
1244 }
1245
1246 #[test]
1247 fn test_same_unit_conversion() {
1248 let result = convert(42.0, "meters", "m").unwrap();
1249 assert!((result - 42.0).abs() < 1e-10);
1250 }
1251
1252 #[test]
1253 fn test_incompatible_units() {
1254 let result = convert(1.0, "kg", "meters");
1255 assert!(result.is_err());
1256 assert!(result.unwrap_err().to_string().contains("Cannot convert"));
1257 }
1258
1259 #[test]
1260 fn test_unknown_unit() {
1261 let result = convert(1.0, "foobar", "meters");
1262 assert!(result.is_err());
1263 assert!(result.unwrap_err().to_string().contains("Unknown unit"));
1264 }
1265
1266 #[test]
1267 fn test_ambiguous_unit_identifier() {
1268 let result = convert(1.0, "ma", "amperes");
1269 assert!(result.is_err());
1270 assert!(result.unwrap_err().to_string().contains("Ambiguous unit"));
1271 }
1272
1273 #[test]
1274 fn test_information_storage() {
1275 let result = convert(1.0, "kibibytes", "bytes").unwrap();
1276 assert!((result - 1024.0).abs() < 1e-10);
1277 }
1278
1279 #[test]
1280 fn test_case_insensitive() {
1281 let result = convert(1.0, "KM", "M").unwrap();
1282 assert!((result - 1000.0).abs() < 1e-10);
1283 }
1284
1285 #[test]
1286 fn test_angle_conversion() {
1287 let result = convert(180.0, "degrees", "radians").unwrap();
1288 assert!((result - std::f64::consts::PI).abs() < 1e-10);
1289 }
1290
1291 #[test]
1292 fn test_area_conversion() {
1293 let result = convert(1.0, "square kilometers", "square meters").unwrap();
1294 assert!((result - 1e6).abs() < 1e-6);
1295 }
1296
1297 #[test]
1298 fn test_concentration_mass_conversion() {
1299 let result = convert(1.0, "grams per liter", "milligrams per deciliter").unwrap();
1300 assert!((result - 100.0).abs() < 1e-10);
1301 }
1302
1303 #[test]
1304 fn test_duration_conversion() {
1305 let result = convert(1.0, "hours", "seconds").unwrap();
1306 assert!((result - 3600.0).abs() < 1e-10);
1307 }
1308
1309 #[test]
1310 fn test_electric_charge_conversion() {
1311 let result = convert(1.0, "ampere hours", "coulombs").unwrap();
1312 assert!((result - 3600.0).abs() < 1e-10);
1313 }
1314
1315 #[test]
1316 fn test_electric_current_conversion() {
1317 let result = convert(1.0, "kiloamperes", "amperes").unwrap();
1318 assert!((result - 1000.0).abs() < 1e-10);
1319 }
1320
1321 #[test]
1322 fn test_electric_potential_difference_conversion() {
1323 let result = convert(1.0, "kilovolts", "volts").unwrap();
1324 assert!((result - 1000.0).abs() < 1e-10);
1325 }
1326
1327 #[test]
1328 fn test_electric_resistance_conversion() {
1329 let result = convert(1.0, "kiloohms", "ohms").unwrap();
1330 assert!((result - 1000.0).abs() < 1e-10);
1331 }
1332
1333 #[test]
1334 fn test_energy_conversion() {
1335 let result = convert(1.0, "kilocalories", "calories").unwrap();
1336 assert!((result - 1000.0).abs() < 1e-10);
1337 }
1338
1339 #[test]
1340 fn test_frequency_conversion() {
1341 let result = convert(1.0, "megahertz", "hertz").unwrap();
1342 assert!((result - 1e6).abs() < 1e-6);
1343 }
1344
1345 #[test]
1346 fn test_fuel_efficiency_conversion() {
1347 let result = convert(10.0, "liters per 100 kilometers", "miles per gallon").unwrap();
1348 assert!((result - 23.521_458_333_333_332).abs() < 1e-9);
1349
1350 let result = convert(7.5, "miles per gallon", "liters per 100 kilometers").unwrap();
1351 assert!((result - (235.214_583_333_333_34 / 7.5)).abs() < 1e-9);
1352
1353 let result = convert(40.0, "miles per gallon", "miles per imperial gallon").unwrap();
1354 assert!((result - 48.037_997_020_194_2).abs() < 1e-9);
1355
1356 let result = convert(
1357 50.0,
1358 "miles per imperial gallon",
1359 "liters per 100 kilometers",
1360 )
1361 .unwrap();
1362 assert!((result - 5.649_618_726_636_444).abs() < 1e-9);
1363 }
1364
1365 #[test]
1366 fn test_power_conversion() {
1367 let result = convert(1.0, "kilowatts", "watts").unwrap();
1368 assert!((result - 1000.0).abs() < 1e-10);
1369 }
1370
1371 #[test]
1372 fn test_pressure_conversion() {
1373 let result = convert(1.0, "bars", "pascals").unwrap();
1374 assert!((result - 100000.0).abs() < 1e-6);
1375 }
1376
1377 #[test]
1378 fn test_speed_conversion() {
1379 let result = convert(1.0, "kilometers per hour", "meters per second").unwrap();
1380 assert!((result - (1.0 / 3.6)).abs() < 1e-10);
1381 }
1382
1383 #[test]
1384 fn test_volume_conversion() {
1385 let result = convert(1.0, "liters", "milliliters").unwrap();
1386 assert!((result - 1000.0).abs() < 1e-10);
1387 }
1388}