whippyunits_core/
units.rs

1use crate::dimension_exponents::{DynDimensionExponents, TypeDimensionExponents};
2use crate::prefix::SiPrefix;
3use crate::scale_exponents::ScaleExponents;
4
5/// Unit system classification.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum System {
8    /// Metric system (SI and derived units)
9    Metric,
10    /// Imperial system (US customary units)
11    Imperial,
12    /// Astronomical system (For extremely )
13    Astronomical,
14}
15
16impl System {
17    /// Get the string identifier for this system.
18    pub const fn as_str(&self) -> &'static str {
19        match self {
20            System::Metric => "Metric",
21            System::Imperial => "Imperial",
22            System::Astronomical => "Astronomical",
23        }
24    }
25}
26
27/// A unit.
28///
29/// Each unit is assigned a "storage unit". The storage unit is the unit
30/// of a value stored with this unit. Storage units are always a well defined
31/// multiple of an SI base unit.
32///
33/// The logarithmic scale encoding in the type system uses only powers of
34/// 2, 3, 5, and pi.  This means that the storage unit must be a multiple of
35/// an SI base unit and a product of powers of 2, 3, 5, and pi.  For example,
36///
37/// - "kilometer" has a scale factor of 10^3 = 2^3 * 5^3
38/// - "degree" has a scale factor of π/180 = 2^-2 * 3^-2 * 5^-1 * pi^1
39///
40/// Units that differ from identity in their `conversion_factor` are "non-storage"
41/// units.  Non-storage units are not stored in their native scale; upon declaration
42/// they are converted to their "nearest neighbor" power-of-10 multiple of a SI
43/// base unit.  For example
44///
45/// - "inch" is multiplied by 2.54 and stored as "centimeters"
46/// - "yard" is multiplied by 0.9144 and stored as "meters"
47/// - "mile" is multiplied by 1.609344 and stored as "kilometers"
48#[derive(Debug, Clone, Copy, PartialEq)]
49pub struct Unit<ExponentsType = DynDimensionExponents> {
50    /// Name of the unit.
51    pub name: &'static str,
52
53    /// Symbols associated with the unit.
54    ///
55    /// Symbols are also used for lookup, so they must be unique within
56    /// the unit system.
57    ///
58    /// Because SI has a systematic prefixing semantics, symbols must be kept
59    /// from colliding not just with the SI base symbols, but with any legal
60    /// prefixing thereof.  "SI base symbols" are defined as the first symbol
61    /// of the first unit in each dimension, by declaration order.
62    pub symbols: &'static [&'static str],
63
64    /// Base unit per storage unit.
65    ///
66    /// To convert from a value in the storage unit to a SI base unit value,
67    /// multiply it by `scale`.
68    ///
69    /// For example, a scale of `1000` in dimension length would store
70    /// the value as kilometers. If we have the value `123` stored then that
71    /// means in meters it is `123 * 1000 = 123,000 meters`.
72    pub scale: ScaleExponents,
73
74    /// Storage unit per this unit.
75    ///
76    /// Difference from identity canonically identifies a unit as a
77    /// "non-storage" unit.  Non-storage units are not stored in their
78    /// native scale, as arbitrary float scaling factors are not part
79    /// of the logarithmic scale encoding in the type system.
80    ///
81    /// To convert from a value in this unit to a storage unit value,
82    /// multiply it by `conversion_factor`.
83    ///
84    /// For example, the "inch" unit has a `conversion_factor` of `2.54`,
85    /// which means that a value of `1` in inches is stored as `2.54`
86    /// (accordingly, the `scale` is `10^-2`).
87    ///
88    /// Non-storage units are always given a storage scale of their
89    /// "nearest neighbor" power-of-10 multiple of a SI base unit.
90    /// For example,
91    ///
92    /// - "inch" is multiplied by 2.54 and stored as "centimeters"
93    /// - "yard" is multiplied by 0.9144 and stored as "meters"
94    /// - "mile" is multiplied by 1.609344 and stored as "kilometers"
95    pub conversion_factor: f64,
96
97    /// The "zero point offset" of this unit's measurement scale from
98    /// the numerical zero of the storage unit.
99    ///
100    /// To convert from a value in the unit to the storage unit, add
101    /// the affine offset to the value.
102    ///
103    /// For example, the "celsius" unit has an affine offset of `273.15`,
104    /// which means that a value of `0` in celsius is stored as `273.15`
105    /// in kelvin.
106    pub affine_offset: f64,
107
108    /// Dimensional exponent vector of the [dimension](crate::dimension_exponents::DimensionBasis)
109    /// this unit belongs to.
110    pub exponents: ExponentsType,
111
112    /// Which "unit system" this unit belongs to.  This determines the name
113    /// of the declarator trait in which this unit's nominal declarators will
114    /// live (e.g. "ImperialLength" or "MetricMass").
115    pub system: System,
116}
117
118const IDENTITY: f64 = 1.0;
119const NONE: f64 = 0.0;
120
121impl<ExponentsType> Unit<ExponentsType> {
122    /// Check if the unit has a conversion factor.
123    pub fn has_conversion(&self) -> bool {
124        self.conversion_factor != IDENTITY
125    }
126
127    /// Check if the unit has an affine offset.
128    pub fn has_affine_offset(&self) -> bool {
129        self.affine_offset != NONE
130    }
131}
132
133impl<
134    const MASS_EXP: i16,
135    const LENGTH_EXP: i16,
136    const TIME_EXP: i16,
137    const CURRENT_EXP: i16,
138    const TEMPERATURE_EXP: i16,
139    const AMOUNT_EXP: i16,
140    const LUMINOSITY_EXP: i16,
141    const ANGLE_EXP: i16,
142>
143    Unit<
144        crate::dimension_exponents!([
145            MASS_EXP,
146            LENGTH_EXP,
147            TIME_EXP,
148            CURRENT_EXP,
149            TEMPERATURE_EXP,
150            AMOUNT_EXP,
151            LUMINOSITY_EXP,
152            ANGLE_EXP,
153        ]),
154    >
155{
156    pub const fn erase(&self) -> Unit {
157        Unit {
158            name: self.name,
159            symbols: self.symbols,
160            scale: self.scale,
161            conversion_factor: self.conversion_factor,
162            affine_offset: self.affine_offset,
163            exponents: self.exponents.value_const(),
164            system: self.system,
165        }
166    }
167}
168
169/// Lists of units.
170impl Unit {
171    pub const BASES: [Self; 8] = [
172        Unit::GRAM.erase(),
173        Unit::METER.erase(),
174        Unit::SECOND.erase(),
175        Unit::AMPERE.erase(),
176        Unit::KELVIN.erase(),
177        Unit::MOLE.erase(),
178        Unit::CANDELA.erase(),
179        Unit::RADIAN.erase(),
180    ];
181
182    /// Convert multiple names to their base unit.
183    ///
184    /// This function takes a scale type name (like "Kilogram", "Millimeter") and returns
185    /// the corresponding base unit (like "g", "m").
186    pub fn multiple_to_base_unit(
187        multiple: &str,
188    ) -> Option<(&'static Self, Option<&'static SiPrefix>)> {
189        let (prefix, name) = SiPrefix::strip_any_prefix_name(multiple)
190            .map(|(prefix, name)| (Some(prefix), name))
191            .unwrap_or((None, multiple));
192
193        Self::BASES.iter().find_map(|unit| {
194            if unit.name == name {
195                Some((unit, prefix))
196            } else {
197                None
198            }
199        })
200    }
201
202    /// Find base unit by symbol.
203    pub fn find_base(symbol: &str) -> Option<&'static Self> {
204        Self::BASES
205            .iter()
206            .find(|unit| unit.symbols.contains(&symbol))
207    }
208}
209
210/// Mass
211impl Unit<crate::dimension_exponents!([1, 0, 0, 0, 0, 0, 0, 0])> {
212    pub const GRAM: Self = Self {
213        name: "gram",
214        symbols: &["g"],
215        scale: ScaleExponents::_10(-3),
216        conversion_factor: IDENTITY,
217        affine_offset: NONE,
218        exponents: TypeDimensionExponents::new(),
219        system: System::Metric,
220    };
221
222    pub const GRAIN: Self = Self {
223        name: "grain",
224        symbols: &["gr"],
225        scale: ScaleExponents::_10(-4),
226        conversion_factor: 0.6479891,
227        affine_offset: NONE,
228        exponents: TypeDimensionExponents::new(),
229        system: System::Imperial,
230    };
231
232    pub const CARAT: Self = Self {
233        name: "carat",
234        symbols: &["ct"],
235        scale: ScaleExponents::_10(-4).mul(ScaleExponents::_2(1)),
236        conversion_factor: IDENTITY,
237        affine_offset: NONE,
238        exponents: TypeDimensionExponents::new(),
239        system: System::Metric,
240    };
241
242    pub const OUNCE: Self = Self {
243        name: "ounce",
244        symbols: &["oz"],
245        scale: ScaleExponents::_10(-2),
246        conversion_factor: 2.8349523125,
247        affine_offset: NONE,
248        exponents: TypeDimensionExponents::new(),
249        system: System::Imperial,
250    };
251
252    pub const TROY_OUNCE: Self = Self {
253        name: "troy_ounce",
254        symbols: &["ozt"],
255        scale: ScaleExponents::_10(-2),
256        conversion_factor: 3.11034768,
257        affine_offset: NONE,
258        exponents: TypeDimensionExponents::new(),
259        system: System::Imperial,
260    };
261    pub const TROY_POUND: Self = Self {
262        name: "troy_pound",
263        symbols: &["lbt"],
264        scale: ScaleExponents::IDENTITY,
265        conversion_factor: 0.3732417216,
266        affine_offset: NONE,
267        exponents: TypeDimensionExponents::new(),
268        system: System::Imperial,
269    };
270
271    pub const POUND: Self = Self {
272        name: "pound",
273        symbols: &["lb"],
274        scale: ScaleExponents::IDENTITY,
275        conversion_factor: 0.45359237,
276        affine_offset: NONE,
277        exponents: TypeDimensionExponents::new(),
278        system: System::Imperial,
279    };
280
281    pub const STONE: Self = Self {
282        name: "stone",
283        symbols: &["st"],
284        scale: ScaleExponents::_10(1),
285        conversion_factor: 0.635029318,
286        affine_offset: NONE,
287        exponents: TypeDimensionExponents::new(),
288        system: System::Imperial,
289    };
290
291    pub const SLUG: Self = Self {
292        name: "slug",
293        symbols: &["slg"],
294        scale: ScaleExponents::_10(1),
295        conversion_factor: 1.4593902937206365,
296        affine_offset: NONE,
297        exponents: TypeDimensionExponents::new(),
298        system: System::Imperial,
299    };
300
301    pub const TON: Self = Self {
302        name: "ton",
303        symbols: &["t"],
304        scale: ScaleExponents::_10(3),
305        conversion_factor: 1.0160469088,
306        affine_offset: NONE,
307        exponents: TypeDimensionExponents::new(),
308        system: System::Imperial,
309    };
310    // To add: Earth mass, Jupiter mass, Sol mass
311}
312
313/// Length
314impl Unit<crate::dimension_exponents!([0, 1, 0, 0, 0, 0, 0, 0])> {
315    pub const METER: Self = Self {
316        name: "meter",
317        symbols: &["m"],
318        scale: ScaleExponents::IDENTITY,
319        conversion_factor: IDENTITY,
320        affine_offset: NONE,
321        exponents: TypeDimensionExponents::new(),
322        system: System::Metric,
323    };
324
325    pub const INCH: Self = Self {
326        name: "inch",
327        symbols: &["in"],
328        scale: ScaleExponents::_10(-2),
329        conversion_factor: 2.54,
330        affine_offset: NONE,
331        exponents: TypeDimensionExponents::new(),
332        system: System::Imperial,
333    };
334
335    pub const FOOT: Self = Self {
336        name: "foot",
337        symbols: &["ft"],
338        scale: ScaleExponents::_10(-1),
339        conversion_factor: 3.048,
340        affine_offset: NONE,
341        exponents: TypeDimensionExponents::new(),
342        system: System::Imperial,
343    };
344
345    pub const YARD: Self = Self {
346        name: "yard",
347        symbols: &["yd"],
348        scale: ScaleExponents::IDENTITY,
349        conversion_factor: 0.9144,
350        affine_offset: NONE,
351        exponents: TypeDimensionExponents::new(),
352        system: System::Imperial,
353    };
354
355    pub const FATHOM: Self = Self {
356        name: "fathom",
357        symbols: &["ftm"],
358        scale: ScaleExponents::IDENTITY,
359        conversion_factor: 1.8288,
360        affine_offset: NONE,
361        exponents: TypeDimensionExponents::new(),
362        system: System::Imperial,
363    };
364
365    pub const FURLONG: Self = Self {
366        name: "furlong",
367        symbols: &["fur"],
368        scale: ScaleExponents::_10(2),
369        conversion_factor: 2.01168,
370        affine_offset: NONE,
371        exponents: TypeDimensionExponents::new(),
372        system: System::Imperial,
373    };
374
375    pub const MILE: Self = Self {
376        name: "mile",
377        symbols: &["mi"],
378        scale: ScaleExponents::_10(3),
379        conversion_factor: 1.609344,
380        affine_offset: NONE,
381        exponents: TypeDimensionExponents::new(),
382        system: System::Imperial,
383    };
384
385    pub const NAUTICAL_MILE: Self = Self {
386        name: "nautical_mile",
387        symbols: &["nmi"],
388        scale: ScaleExponents::_10(3),
389        conversion_factor: 1.852,
390        affine_offset: NONE,
391        exponents: TypeDimensionExponents::new(),
392        system: System::Imperial,
393    };
394
395    pub const ASTRONOMICAL_UNIT: Self = Self {
396        name: "astronomical_unit",
397        symbols: &["AU"],
398        scale: ScaleExponents::_10(11),
399        conversion_factor: 1.495978707,
400        affine_offset: NONE,
401        exponents: TypeDimensionExponents::new(),
402        system: System::Astronomical,
403    };
404
405    pub const LIGHT_YEAR: Self = Self {
406        name: "light_year",
407        symbols: &["ly"],
408        scale: ScaleExponents::_10(16),
409        conversion_factor: 0.94607304725808,
410        affine_offset: NONE,
411        exponents: TypeDimensionExponents::new(),
412        system: System::Astronomical,
413    };
414
415    pub const PARSEC: Self = Self {
416        name: "parsec",
417        symbols: &["pc"],
418        scale: ScaleExponents::_10(16),
419        conversion_factor: 3.08567758128,
420        affine_offset: NONE,
421        exponents: TypeDimensionExponents::new(),
422        system: System::Astronomical,
423    };
424}
425
426/// Time
427impl Unit<crate::dimension_exponents!([0, 0, 1, 0, 0, 0, 0, 0])> {
428    pub const SECOND: Self = Self {
429        name: "second",
430        symbols: &["s"],
431        scale: ScaleExponents::IDENTITY,
432        conversion_factor: IDENTITY,
433        affine_offset: NONE,
434        exponents: TypeDimensionExponents::new(),
435        system: System::Metric,
436    };
437
438    pub const MINUTE: Self = Self {
439        name: "minute",
440        symbols: &["min"],
441        scale: ScaleExponents::_10(1).mul(ScaleExponents::_6(1)),
442        conversion_factor: IDENTITY,
443        affine_offset: NONE,
444        exponents: TypeDimensionExponents::new(),
445        system: System::Metric,
446    };
447
448    pub const HOUR: Self = Self {
449        name: "hour",
450        symbols: &["h", "hr"],
451        scale: ScaleExponents::_10(2).mul(ScaleExponents::_6(2)),
452        conversion_factor: IDENTITY,
453        affine_offset: NONE,
454        exponents: TypeDimensionExponents::new(),
455        system: System::Metric,
456    };
457
458    pub const DAY: Self = Self {
459        name: "day",
460        symbols: &["d"],
461        scale: ScaleExponents::_10(2)
462            .mul(ScaleExponents::_6(3))
463            .mul(ScaleExponents::_2(2)),
464        conversion_factor: IDENTITY,
465        affine_offset: NONE,
466        exponents: TypeDimensionExponents::new(),
467        system: System::Metric,
468    };
469
470    pub const WEEK: Self = Self {
471        name: "week",
472        symbols: &["wk"],
473        scale: ScaleExponents::_10(3)
474            .mul(ScaleExponents::_6(3))
475            .mul(ScaleExponents::_2(2)),
476        conversion_factor: 0.7,
477        affine_offset: NONE,
478        exponents: TypeDimensionExponents::new(),
479        system: System::Metric,
480    };
481
482    pub const MONTH: Self = Self {
483        // 30 days
484        name: "month",
485        symbols: &["mo"],
486        scale: ScaleExponents::_10(3)
487            .mul(ScaleExponents::_6(4))
488            .mul(ScaleExponents::_2(1)),
489        conversion_factor: IDENTITY,
490        affine_offset: NONE,
491        exponents: TypeDimensionExponents::new(),
492        system: System::Metric,
493    };
494
495    pub const YEAR: Self = Self {
496        // solar year, not calendar year
497        name: "year",
498        symbols: &["yr"],
499        scale: ScaleExponents::_10(7),
500        conversion_factor: 3.1556926,
501        affine_offset: NONE,
502        exponents: TypeDimensionExponents::new(),
503        system: System::Metric,
504    };
505}
506
507/// Current
508impl Unit<crate::dimension_exponents!([0, 0, 0, 1, 0, 0, 0, 0])> {
509    pub const AMPERE: Self = Self {
510        name: "ampere",
511        symbols: &["A"],
512        scale: ScaleExponents::IDENTITY,
513        conversion_factor: IDENTITY,
514        affine_offset: NONE,
515        exponents: TypeDimensionExponents::new(),
516        system: System::Metric,
517    };
518}
519
520/// Temperature
521impl Unit<crate::dimension_exponents!([0, 0, 0, 0, 1, 0, 0, 0])> {
522    pub const KELVIN: Self = Self {
523        name: "kelvin",
524        symbols: &["K"],
525        scale: ScaleExponents::IDENTITY,
526        conversion_factor: IDENTITY,
527        affine_offset: NONE,
528        exponents: TypeDimensionExponents::new(),
529        system: System::Metric,
530    };
531
532    pub const CELSIUS: Self = Self {
533        name: "celsius",
534        symbols: &["degC"],
535        scale: ScaleExponents::IDENTITY,
536        conversion_factor: IDENTITY,
537        affine_offset: 273.15,
538        exponents: TypeDimensionExponents::new(),
539        system: System::Metric,
540    };
541
542    pub const RANKINE: Self = Self {
543        name: "rankine",
544        symbols: &["degR"],
545        scale: ScaleExponents([0, -2, 1, 0]),
546        conversion_factor: IDENTITY,
547        affine_offset: NONE,
548        exponents: TypeDimensionExponents::new(),
549        system: System::Imperial,
550    };
551
552    pub const FAHRENHEIT: Self = Self {
553        name: "fahrenheit",
554        symbols: &["degF"],
555        scale: ScaleExponents([0, -2, 1, 0]),
556        conversion_factor: IDENTITY,
557        affine_offset: 459.7,
558        exponents: TypeDimensionExponents::new(),
559        system: System::Imperial,
560    };
561}
562
563/// Amount
564impl Unit<crate::dimension_exponents!([0, 0, 0, 0, 0, 1, 0, 0])> {
565    pub const MOLE: Self = Self {
566        name: "mole",
567        symbols: &["mol"],
568        scale: ScaleExponents::IDENTITY,
569        conversion_factor: IDENTITY,
570        affine_offset: NONE,
571        exponents: TypeDimensionExponents::new(),
572        system: System::Metric,
573    };
574}
575
576/// Angle
577impl Unit<crate::dimension_exponents!([0, 0, 0, 0, 0, 0, 0, 1])> {
578    pub const RADIAN: Self = Self {
579        name: "radian",
580        symbols: &["rad"],
581        scale: ScaleExponents::IDENTITY,
582        conversion_factor: IDENTITY,
583        affine_offset: NONE,
584        exponents: TypeDimensionExponents::new(),
585        system: System::Metric,
586    };
587
588    pub const DEGREE: Self = Self {
589        name: "degree",
590        symbols: &["deg"],
591        scale: ScaleExponents([-2, -2, -1, 1]),
592        conversion_factor: IDENTITY,
593        affine_offset: NONE,
594        exponents: TypeDimensionExponents::new(),
595        system: System::Metric,
596    };
597
598    pub const GRADIAN: Self = Self {
599        name: "gradian",
600        symbols: &["grad"],
601        scale: ScaleExponents([-3, -1, -1, 1]),
602        conversion_factor: IDENTITY,
603        affine_offset: NONE,
604        exponents: TypeDimensionExponents::new(),
605        system: System::Metric,
606    };
607
608    pub const TURN: Self = Self {
609        name: "turn",
610        symbols: &["rot", "turn"],
611        scale: ScaleExponents([1, 0, 0, 1]),
612        conversion_factor: IDENTITY,
613        affine_offset: NONE,
614        exponents: TypeDimensionExponents::new(),
615        system: System::Metric,
616    };
617
618    pub const ARCMINUTE: Self = Self {
619        name: "arcminute",
620        symbols: &["arcmin"],
621        scale: ScaleExponents([-4, -2, -2, 1]),
622        conversion_factor: IDENTITY,
623        affine_offset: NONE,
624        exponents: TypeDimensionExponents::new(),
625        system: System::Metric,
626    };
627
628    pub const ARCSECOND: Self = Self {
629        name: "arcsecond",
630        symbols: &["arcsec"],
631        scale: ScaleExponents([-6, -2, -2, 1]),
632        conversion_factor: IDENTITY,
633        affine_offset: NONE,
634        exponents: TypeDimensionExponents::new(),
635        system: System::Metric,
636    };
637}
638
639/// Frequency
640impl Unit<crate::dimension_exponents!([0, 0, -1, 0, 0, 0, 0, 0])> {
641    pub const HERTZ: Self = Self {
642        name: "hertz",
643        symbols: &["Hz"],
644        scale: ScaleExponents::IDENTITY,
645        conversion_factor: IDENTITY,
646        affine_offset: NONE,
647        exponents: TypeDimensionExponents::new(),
648        system: System::Metric,
649    };
650}
651
652/// Force
653impl Unit<crate::dimension_exponents!([1, 1, -2, 0, 0, 0, 0, 0])> {
654    pub const NEWTON: Self = Self {
655        name: "newton",
656        symbols: &["N"],
657        scale: ScaleExponents::IDENTITY,
658        conversion_factor: IDENTITY,
659        affine_offset: NONE,
660        exponents: TypeDimensionExponents::new(),
661        system: System::Metric,
662    };
663}
664
665/// Energy and work
666impl Unit<crate::dimension_exponents!([1, 2, -2, 0, 0, 0, 0, 0])> {
667    pub const JOULE: Self = Self {
668        name: "joule",
669        symbols: &["J"],
670        scale: ScaleExponents::IDENTITY,
671        conversion_factor: IDENTITY,
672        affine_offset: NONE,
673        exponents: TypeDimensionExponents::new(),
674        system: System::Metric,
675    };
676
677    pub const NEWTON_METER: Self = Self {
678        name: "newton_meter",
679        symbols: &["Nm"],
680        scale: ScaleExponents::IDENTITY,
681        conversion_factor: IDENTITY,
682        affine_offset: NONE,
683        exponents: TypeDimensionExponents::new(),
684        system: System::Metric,
685    };
686
687    pub const ELECTRONVOLT: Self = Self {
688        name: "electron_volt",
689        symbols: &["eV"],
690        scale: ScaleExponents::_10(-19),
691        conversion_factor: 1.602176634,
692        affine_offset: NONE,
693        exponents: TypeDimensionExponents::new(),
694        system: System::Metric,
695    };
696
697    pub const ERG: Self = Self {
698        name: "erg",
699        symbols: &["erg"],
700        scale: ScaleExponents::_10(-7),
701        conversion_factor: 1.0,
702        affine_offset: NONE,
703        exponents: TypeDimensionExponents::new(),
704        system: System::Metric,
705    };
706
707    pub const CALORIE: Self = Self {
708        name: "calorie",
709        symbols: &["cal"],
710        scale: ScaleExponents::_10(1),
711        conversion_factor: 0.4184,
712        affine_offset: NONE,
713        exponents: TypeDimensionExponents::new(),
714        system: System::Metric,
715    };
716
717    pub const FOOT_POUND: Self = Self {
718        name: "foot_pound",
719        symbols: &["ft_lb"],
720        scale: ScaleExponents::_10(1),
721        conversion_factor: 1.3558179483314004,
722        affine_offset: NONE,
723        exponents: TypeDimensionExponents::new(),
724        system: System::Imperial,
725    };
726
727    pub const KILOWATT_HOUR: Self = Self {
728        name: "kilowatt_hour",
729        symbols: &["kWh"],
730        scale: ScaleExponents::_10(5).mul(ScaleExponents::_6(2)),
731        conversion_factor: IDENTITY,
732        affine_offset: NONE,
733        exponents: TypeDimensionExponents::new(),
734        system: System::Metric,
735    };
736
737    pub const THERM: Self = Self {
738        name: "therm",
739        symbols: &["thm"],
740        scale: ScaleExponents::_10(8),
741        conversion_factor: 1.05505585262,
742        affine_offset: NONE,
743        exponents: TypeDimensionExponents::new(),
744        system: System::Imperial,
745    };
746}
747
748/// Power
749impl Unit<crate::dimension_exponents!([1, 2, -3, 0, 0, 0, 0, 0])> {
750    pub const WATT: Self = Self {
751        name: "watt",
752        symbols: &["W"],
753        scale: ScaleExponents::IDENTITY,
754        conversion_factor: IDENTITY,
755        affine_offset: NONE,
756        exponents: TypeDimensionExponents::new(),
757        system: System::Metric,
758    };
759
760    pub const HORSEPOWER: Self = Self {
761        name: "horsepower",
762        symbols: &["hp"],
763        scale: ScaleExponents::_10(3),
764        conversion_factor: 0.7456998715822702,
765        affine_offset: NONE,
766        exponents: TypeDimensionExponents::new(),
767        system: System::Imperial,
768    };
769}
770
771/// Pressure
772impl Unit<crate::dimension_exponents!([1, -1, -2, 0, 0, 0, 0, 0])> {
773    pub const PASCAL: Self = Self {
774        name: "pascal",
775        symbols: &["Pa"],
776        scale: ScaleExponents::IDENTITY,
777        conversion_factor: IDENTITY,
778        affine_offset: NONE,
779        exponents: TypeDimensionExponents::new(),
780        system: System::Metric,
781    };
782
783    pub const TORR: Self = Self {
784        name: "torr",
785        symbols: &["Torr"],
786        scale: ScaleExponents::_10(2),
787        conversion_factor: 1.3332236842105263,
788        affine_offset: NONE,
789        exponents: TypeDimensionExponents::new(),
790        system: System::Metric,
791    };
792
793    pub const PSI: Self = Self {
794        name: "psi",
795        symbols: &["psi"],
796        scale: ScaleExponents::_10(4),
797        conversion_factor: 0.6894757293168361,
798        affine_offset: NONE,
799        exponents: TypeDimensionExponents::new(),
800        system: System::Imperial,
801    };
802
803    pub const BAR: Self = Self {
804        name: "bar",
805        symbols: &["bar"],
806        scale: ScaleExponents::_10(5),
807        conversion_factor: IDENTITY,
808        affine_offset: NONE,
809        exponents: TypeDimensionExponents::new(),
810        system: System::Metric,
811    };
812
813    pub const ATMOSPHERE: Self = Self {
814        name: "atmosphere",
815        symbols: &["atm"],
816        scale: ScaleExponents::_10(5),
817        conversion_factor: 1.01325,
818        affine_offset: NONE,
819        exponents: TypeDimensionExponents::new(),
820        system: System::Metric,
821    };
822}
823
824/// Electric charge
825impl Unit<crate::dimension_exponents!([0, 0, 1, 1, 0, 0, 0, 0])> {
826    pub const COULOMB: Self = Self {
827        name: "coulomb",
828        symbols: &["C"],
829        scale: ScaleExponents::IDENTITY,
830        conversion_factor: IDENTITY,
831        affine_offset: NONE,
832        exponents: TypeDimensionExponents::new(),
833        system: System::Metric,
834    };
835}
836
837/// Electric potential
838impl Unit<crate::dimension_exponents!([1, 2, -3, -1, 0, 0, 0, 0])> {
839    pub const VOLT: Self = Self {
840        name: "volt",
841        symbols: &["V"],
842        scale: ScaleExponents::IDENTITY,
843        conversion_factor: IDENTITY,
844        affine_offset: NONE,
845        exponents: TypeDimensionExponents::new(),
846        system: System::Metric,
847    };
848}
849
850/// Capacitance
851impl Unit<crate::dimension_exponents!([-1, -2, 4, 2, 0, 0, 0, 0])> {
852    pub const FARAD: Self = Self {
853        name: "farad",
854        symbols: &["F"],
855        scale: ScaleExponents::IDENTITY,
856        conversion_factor: IDENTITY,
857        affine_offset: NONE,
858        exponents: TypeDimensionExponents::new(),
859        system: System::Metric,
860    };
861}
862
863// Electric resistance
864impl Unit<crate::dimension_exponents!([1, 2, -3, -2, 0, 0, 0, 0])> {
865    pub const OHM: Self = Self {
866        name: "ohm",
867        symbols: &["Ω"],
868        scale: ScaleExponents::IDENTITY,
869        conversion_factor: IDENTITY,
870        affine_offset: NONE,
871        exponents: TypeDimensionExponents::new(),
872        system: System::Metric,
873    };
874}
875
876/// Electric conductance
877impl Unit<crate::dimension_exponents!([-1, -2, 3, 2, 0, 0, 0, 0])> {
878    pub const SIEMENS: Self = Self {
879        name: "siemens",
880        symbols: &["S"],
881        scale: ScaleExponents::IDENTITY,
882        conversion_factor: IDENTITY,
883        affine_offset: NONE,
884        exponents: TypeDimensionExponents::new(),
885        system: System::Metric,
886    };
887}
888
889/// Inductance
890impl Unit<crate::dimension_exponents!([1, 2, -2, -2, 0, 0, 0, 0])> {
891    pub const HENRY: Self = Self {
892        name: "henry",
893        symbols: &["H"],
894        scale: ScaleExponents::IDENTITY,
895        conversion_factor: IDENTITY,
896        affine_offset: NONE,
897        exponents: TypeDimensionExponents::new(),
898        system: System::Metric,
899    };
900}
901
902/// Magnetic field
903impl Unit<crate::dimension_exponents!([1, 0, -2, -1, 0, 0, 0, 0])> {
904    pub const TESLA: Self = Self {
905        name: "tesla",
906        symbols: &["T"],
907        scale: ScaleExponents::IDENTITY,
908        conversion_factor: IDENTITY,
909        affine_offset: NONE,
910        exponents: TypeDimensionExponents::new(),
911        system: System::Metric,
912    };
913
914    pub const GAUSS: Self = Self {
915        name: "gauss",
916        symbols: &["G"],
917        scale: ScaleExponents::_10(-4),
918        conversion_factor: IDENTITY,
919        affine_offset: NONE,
920        exponents: TypeDimensionExponents::new(),
921        system: System::Metric,
922    };
923}
924
925/// Magnetic flex
926impl Unit<crate::dimension_exponents!([1, 2, -2, -1, 0, 0, 0, 0])> {
927    pub const WEBER: Self = Self {
928        name: "weber",
929        symbols: &["Wb"],
930        scale: ScaleExponents::IDENTITY,
931        conversion_factor: IDENTITY,
932        affine_offset: NONE,
933        exponents: TypeDimensionExponents::new(),
934        system: System::Metric,
935    };
936}
937
938/// Illuminance
939impl Unit<crate::dimension_exponents!([0, -2, 0, 0, 0, 0, 1, 0])> {
940    pub const LUX: Self = Self {
941        name: "lux",
942        symbols: &["lx"],
943        scale: ScaleExponents::IDENTITY,
944        conversion_factor: IDENTITY,
945        affine_offset: NONE,
946        exponents: TypeDimensionExponents::new(),
947        system: System::Metric,
948    };
949
950    pub const LUMEN: Self = Self {
951        name: "lumen",
952        symbols: &["lm"],
953        scale: ScaleExponents::IDENTITY,
954        conversion_factor: IDENTITY,
955        affine_offset: NONE,
956        exponents: TypeDimensionExponents::new(),
957        system: System::Metric,
958    };
959}
960
961/// Luminous intensity
962impl Unit<crate::dimension_exponents!([0, 0, 0, 0, 0, 0, 1, 0])> {
963    pub const CANDELA: Self = Self {
964        name: "candela",
965        symbols: &["cd"],
966        scale: ScaleExponents::IDENTITY,
967        conversion_factor: IDENTITY,
968        affine_offset: NONE,
969        exponents: TypeDimensionExponents::new(),
970        system: System::Metric,
971    };
972}
973
974/// Kinematic viscosity
975impl Unit<crate::dimension_exponents!([0, 2, -1, 0, 0, 0, 0, 0])> {
976    pub const STOKES: Self = Self {
977        name: "stokes",
978        symbols: &["St"],
979        scale: ScaleExponents::_10(-4),
980        conversion_factor: IDENTITY,
981        affine_offset: NONE,
982        exponents: TypeDimensionExponents::new(),
983        system: System::Metric,
984    };
985}
986
987/// Area
988impl Unit<crate::dimension_exponents!([0, 2, 0, 0, 0, 0, 0, 0])> {
989    pub const HECTARE: Self = Self {
990        name: "hectare",
991        symbols: &["hect"],
992        scale: ScaleExponents::_10(4),
993        conversion_factor: IDENTITY, // 1 hectare = 10,000 m²
994        affine_offset: NONE,
995        exponents: TypeDimensionExponents::new(),
996        system: System::Metric,
997    };
998
999    pub const ACRE: Self = Self {
1000        name: "acre",
1001        symbols: &["acre"],
1002        scale: ScaleExponents::_10(3),
1003        conversion_factor: 0.40468564224, // 1 acre = 4046.8564224 m²
1004        affine_offset: NONE,
1005        exponents: TypeDimensionExponents::new(),
1006        system: System::Imperial,
1007    };
1008}
1009
1010/// Volume
1011impl Unit<crate::dimension_exponents!([0, 3, 0, 0, 0, 0, 0, 0])> {
1012    // Metric volume units
1013    pub const LITER: Self = Self {
1014        name: "liter",
1015        symbols: &["L", "l"],
1016        scale: ScaleExponents::_10(-3),
1017        conversion_factor: IDENTITY,
1018        affine_offset: NONE,
1019        exponents: TypeDimensionExponents::new(),
1020        system: System::Metric,
1021    };
1022
1023    pub const GALLON_US: Self = Self {
1024        name: "gallon",
1025        symbols: &["gal", "gallon"],
1026        scale: ScaleExponents::_10(-2),
1027        conversion_factor: 0.3785411784, // 1 US gallon = 3.785411784 L
1028        affine_offset: NONE,
1029        exponents: TypeDimensionExponents::new(),
1030        system: System::Imperial,
1031    };
1032
1033    pub const GALLON_UK: Self = Self {
1034        name: "uk_gallon",
1035        symbols: &["uk_gal"],
1036        scale: ScaleExponents::_10(-2),
1037        conversion_factor: 0.454609, // 1 UK gallon = 4.54609 L
1038        affine_offset: NONE,
1039        exponents: TypeDimensionExponents::new(),
1040        system: System::Imperial,
1041    };
1042
1043    pub const QUART_US: Self = Self {
1044        name: "quart",
1045        symbols: &["qrt"],
1046        scale: ScaleExponents::_10(-3),
1047        conversion_factor: 0.946352946, // 1 US quart = 0.946352946 L
1048        affine_offset: NONE,
1049        exponents: TypeDimensionExponents::new(),
1050        system: System::Imperial,
1051    };
1052
1053    pub const QUART_UK: Self = Self {
1054        name: "uk_quart",
1055        symbols: &["uk_qrt"],
1056        scale: ScaleExponents::_10(-3),
1057        conversion_factor: 1.1365225, // 1 UK quart = 1.1365225 L
1058        affine_offset: NONE,
1059        exponents: TypeDimensionExponents::new(),
1060        system: System::Imperial,
1061    };
1062
1063    pub const PINT_US: Self = Self {
1064        name: "pint",
1065        symbols: &["pnt"],
1066        scale: ScaleExponents::_10(-3),
1067        conversion_factor: 0.473176473, // 1 US pint = 0.473176473 L
1068        affine_offset: NONE,
1069        exponents: TypeDimensionExponents::new(),
1070        system: System::Imperial,
1071    };
1072
1073    pub const PINT_UK: Self = Self {
1074        name: "uk_pint",
1075        symbols: &["uk_pnt"],
1076        scale: ScaleExponents::_10(-3),
1077        conversion_factor: 0.56826125, // 1 UK pint = 0.56826125 L
1078        affine_offset: NONE,
1079        exponents: TypeDimensionExponents::new(),
1080        system: System::Imperial,
1081    };
1082
1083    pub const CUP_US: Self = Self {
1084        name: "cup",
1085        symbols: &["cup"],
1086        scale: ScaleExponents::_10(-4),
1087        conversion_factor: 2.365882365, // 1 US cup = 0.2365882365 L
1088        affine_offset: NONE,
1089        exponents: TypeDimensionExponents::new(),
1090        system: System::Imperial,
1091    };
1092
1093    pub const CUP_UK: Self = Self {
1094        name: "uk_cup",
1095        symbols: &["uk_cup"],
1096        scale: ScaleExponents::_10(-4),
1097        conversion_factor: 2.84130625, // 1 UK cup = 0.284130625 L
1098        affine_offset: NONE,
1099        exponents: TypeDimensionExponents::new(),
1100        system: System::Imperial,
1101    };
1102
1103    pub const FLUID_OUNCE_US: Self = Self {
1104        name: "fluid_ounce",
1105        symbols: &["fl_oz"],
1106        scale: ScaleExponents::_10(-5),
1107        conversion_factor: 2.95735295625,
1108        affine_offset: NONE,
1109        exponents: TypeDimensionExponents::new(),
1110        system: System::Imperial,
1111    };
1112
1113    pub const FLUID_OUNCE_UK: Self = Self {
1114        name: "uk_fluid_ounce",
1115        symbols: &["uk_fl_oz"],
1116        scale: ScaleExponents::_10(-5),
1117        conversion_factor: 2.84130625,
1118        affine_offset: NONE,
1119        exponents: TypeDimensionExponents::new(),
1120        system: System::Imperial,
1121    };
1122
1123    pub const METRIC_FLUID_OUNCE: Self = Self {
1124        name: "metric_fluid_ounce",
1125        symbols: &["metric_fl_oz"],
1126        scale: ScaleExponents::_10(-5).mul(ScaleExponents::_3(1)),
1127        conversion_factor: IDENTITY,
1128        affine_offset: NONE,
1129        exponents: TypeDimensionExponents::new(),
1130        system: System::Metric,
1131    };
1132
1133    pub const TABLESPOON_US: Self = Self {
1134        name: "tablespoon",
1135        symbols: &["tbsp"],
1136        scale: ScaleExponents::_10(-5),
1137        conversion_factor: 1.478676478125,
1138        affine_offset: NONE,
1139        exponents: TypeDimensionExponents::new(),
1140        system: System::Imperial,
1141    };
1142
1143    pub const TABLESPOON_UK: Self = Self {
1144        name: "uk_tablespoon",
1145        symbols: &["uk_tbsp"],
1146        scale: ScaleExponents::_10(-5),
1147        conversion_factor: 1.77581640625,
1148        affine_offset: NONE,
1149        exponents: TypeDimensionExponents::new(),
1150        system: System::Imperial,
1151    };
1152
1153    pub const TEASPOON_US: Self = Self {
1154        name: "teaspoon",
1155        symbols: &["tsp"],
1156        scale: ScaleExponents::_10(-5),
1157        conversion_factor: 0.492892159375,
1158        affine_offset: NONE,
1159        exponents: TypeDimensionExponents::new(),
1160        system: System::Imperial,
1161    };
1162
1163    pub const TEASPOON_UK: Self = Self {
1164        name: "uk_teaspoon",
1165        symbols: &["uk_tsp"],
1166        scale: ScaleExponents::_10(-5),
1167        conversion_factor: 0.59193880208333,
1168        affine_offset: NONE,
1169        exponents: TypeDimensionExponents::new(),
1170        system: System::Imperial,
1171    };
1172
1173    pub const BUSHEL: Self = Self {
1174        name: "bushel",
1175        symbols: &["bu"],
1176        scale: ScaleExponents::_10(-1),
1177        conversion_factor: 0.3523907016688,
1178        affine_offset: NONE,
1179        exponents: TypeDimensionExponents::new(),
1180        system: System::Imperial,
1181    };
1182}
1183
1184/// Dimensionless
1185impl Unit<crate::dimension_exponents!([0, 0, 0, 0, 0, 0, 0, 0])> {
1186    pub const DIMENSIONLESS: Self = Self {
1187        name: "dimensionless",
1188        symbols: &[],
1189        scale: ScaleExponents::IDENTITY,
1190        conversion_factor: IDENTITY,
1191        affine_offset: NONE,
1192        exponents: TypeDimensionExponents::new(),
1193        system: System::Metric,
1194    };
1195}
1196
1197#[cfg(test)]
1198mod tests {
1199    use super::*;
1200
1201    #[test]
1202    fn can_get_prefix_and_unit_from_multiple_string() {
1203        assert_eq!(
1204            Unit::multiple_to_base_unit("kilometer").unwrap(),
1205            (&Unit::METER.erase(), Some(&SiPrefix::KILO),)
1206        );
1207
1208        assert_eq!(
1209            Unit::multiple_to_base_unit("millisecond").unwrap(),
1210            (&Unit::SECOND.erase(), Some(&SiPrefix::MILLI),)
1211        );
1212
1213        assert_eq!(
1214            Unit::multiple_to_base_unit("gram").unwrap(),
1215            (&Unit::GRAM.erase(), None,)
1216        );
1217
1218        assert_eq!(Unit::multiple_to_base_unit("abc"), None,);
1219    }
1220}