ucum/
lib.rs

1//! UCUM is the [Unified Code for Units of Measure](https://unitsofmeasure.org/).
2//!
3//! This crate is a (partial) implementation of the revision 442 of the
4//! [UCUM specification](http://unitsofmeasure.org/ucum.html).
5//!
6//! # Quick start
7//! ```
8//! use ucum::prelude::*;
9//!
10//! let system = UnitSystem::<f64>::default();
11//! let q1 = system.parse("35.5 km/h")?;
12//! let q2 = system.parse("1.1e1 m.s-1")?;
13//! assert!(q1 < q2);
14//! assert!(2*q1 > q2);
15//! # Ok::<(), Box<dyn std::error::Error>>(())
16//! ```
17//!
18//! # Implementation status
19//!
20//! ## Syntax and semantics
21//!
22//! + [x] Parser for UCUM complex units
23//! + [x] Semantics of standard units
24//! + [x] Semantics of [special units](http://unitsofmeasure.org/ucum.html#section-Special-Units-on-non-ratio-Scales)
25//! + [ ] Semantics of [arbitrary units](http://unitsofmeasure.org/ucum.html#section-Arbitrary-Units)
26//!
27//! ## Unit tables
28//!
29//! + [x] [Table 1: prefix symbols](http://unitsofmeasure.org/ucum.html#prefixes)
30//! + [x] [Table 2: base units](http://unitsofmeasure.org/ucum.html#baseunits)
31//! + [x] [Table 3: dimensionless units](http://unitsofmeasure.org/ucum.html#prefixes)
32//! + [x] [Table 4: SI units](http://unitsofmeasure.org/ucum.html#prefixes)
33//! + [x] [Table 5: Other units from ISO 1000, ISO 2955, and some from ANSI X3.50](http://unitsofmeasure.org/ucum.html#iso1000)
34//! + [ ] [Table 6: Natural units](http://unitsofmeasure.org/ucum.html#const)
35//! + [ ] [Table 7: CGS units](http://unitsofmeasure.org/ucum.html#cgs)
36//! + [ ] [Table 8: International customary units](http://unitsofmeasure.org/ucum.html#intcust)
37//! + [ ] [Table 9: Older U.S. “survey” lengths (also called "statute" lengths)](http://unitsofmeasure.org/ucum.html#us-lengths)
38//! + [ ] [Table 10: British Imperial lengths](http://unitsofmeasure.org/ucum.html#brit-length)
39//! + [ ] [Table 11: U.S. volumes including so called “dry measures”](http://unitsofmeasure.org/ucum.html#us-volumes)
40//! + [ ] [Table 12: British Imperial volumes](http://unitsofmeasure.org/ucum.html#brit-volumes)
41//! + [ ] [Table 13: Avoirdupois weights](http://unitsofmeasure.org/ucum.html#avoirdupois)
42//! + [ ] [Table 14: Troy weights](http://unitsofmeasure.org/ucum.html#troy)
43//! + [ ] [Table 15: Apothecaries' weights](http://unitsofmeasure.org/ucum.html#apoth)
44//! + [ ] [Table 16: Units used in typesetting](http://unitsofmeasure.org/ucum.html#typeset)
45//! + [ ] [Table 17: Other Units for Heat and Temperature](http://unitsofmeasure.org/ucum.html#heat)
46//! + [ ] [Table 18: Units Used Predominantly in Clinical Medicine](http://unitsofmeasure.org/ucum.html#clinical)
47//! + [ ] [Table 19: Units used in Chemical and Biomedical Laboratories](http://unitsofmeasure.org/ucum.html#chemical)
48//! + [ ] [Table 20: Levels](http://unitsofmeasure.org/ucum.html#levels)
49//! + [ ] [Table 21: Miscellaneous Units](http://unitsofmeasure.org/ucum.html#misc)
50//! + [ ] [Table 22: Units used in Information Science and Technology](http://unitsofmeasure.org/ucum.html#infotech)
51//! + [ ] [Table 23: The special prefix symbols for powers of 2](http://unitsofmeasure.org/ucum.html#infopfx)
52//!
53//! ## Misc
54//!
55//! + [ ] Full crate documentation
56
57// uncomment this find missing documentations
58//#![deny(missing_docs)]
59
60use std::ops::{Add, Div, Mul, MulAssign, Sub};
61use std::str::FromStr;
62
63#[cfg(test)]
64/// Macro to check equality of floats with a % of error
65macro_rules! assert_eq_err {
66    ($lhs: expr, $rhs: expr, $err: expr) => {
67        let lhs = $lhs as f64;
68        let rhs = $rhs as f64;
69        if (1.0 - lhs / rhs).abs() >= $err {
70            dbg!(lhs);
71            dbg!(rhs);
72            assert!(false);
73        }
74    };
75    ($lhs: expr, $rhs: expr) => {
76        assert_eq_err!($lhs, $rhs, 1e-6)
77    };
78}
79
80#[cfg(test)]
81/// Macro to check equality of quantities with a % of error
82macro_rules! assert_eq_q {
83    ($lhs: expr, $rhs: expr, $err: expr) => {
84        assert_eq!($lhs.dimension(), $rhs.dimension());
85        assert_eq_err!($lhs.magnitude(), $rhs.magnitude(), $err);
86    };
87    ($lhs: expr, $rhs: expr) => {
88        assert_eq_q!($lhs, $rhs, 1e-6)
89    };
90}
91
92pub mod ast;
93use ast::*;
94
95pub mod dimension;
96use dimension::*;
97
98pub mod error;
99use error::*;
100
101pub mod parser;
102use parser::*;
103
104pub mod quantity;
105use quantity::*;
106
107pub mod special;
108
109pub mod system;
110use system::*;
111
112/// Re-exports most useful symbols from the `ucum` crate.
113pub mod prelude {
114    pub use crate::quantity::Quantity;
115    pub use crate::system::{UnitSystem, UnitSystemFactory};
116}
117
118#[cfg(test)]
119mod test {
120    use super::*;
121    use test_case::test_case;
122
123    // prefixes
124    #[test_case("1000 ms", "1 s")]
125    // prefixes with exponents
126    #[test_case("2 dam2", "200m2")]
127    #[test_case("200 dm2", "2m2")]
128    // semantic equivalent
129    #[test_case("3J", "3 kg.m2/s2")]
130    #[test_case("3kHz", "3000s-1")]
131    // syntactic variations
132    #[test_case("40 m/s", "40 m.s-1")]
133    #[test_case("41 m/s2", "41 m.s-2")]
134    #[test_case("42 m/s2", "42 m/(s.s)")]
135    #[test_case("43 m/s2", "43 s-2/m-1")]
136    #[test_case("44 m/s2", "4 s-2.11/m-1")]
137    fn equivalent(txt1: &str, txt2: &str) {
138        let system = UnitSystem::<f64>::default();
139        let q1 = system.parse(txt1).unwrap();
140        let q2 = system.parse(txt2).unwrap();
141        assert_eq!(q1.dimension(), q2.dimension());
142        assert_eq_err!(q1.magnitude(), q2.magnitude(), 1e-6);
143    }
144
145    #[test_case("1m", "2m")]
146    #[test_case("10cm", "2m")]
147    #[test_case("1e6um", "2m")]
148    fn commensurable(txt1: &str, txt2: &str) {
149        let system = UnitSystem::<f64>::default();
150        let q1 = system.parse(txt1).unwrap();
151        let q2 = system.parse(txt2).unwrap();
152        assert!(q1 <= q2);
153        assert!(!(q1 >= q2));
154    }
155
156    #[test_case("1m", "1g")]
157    #[test_case("1J", "1g")]
158    #[test_case("1J", "1W")]
159    fn not_commensurable(txt1: &str, txt2: &str) {
160        let system = UnitSystem::<f64>::default();
161        let q1 = system.parse(txt1).unwrap();
162        let q2 = system.parse(txt2).unwrap();
163        assert!(!(q1 <= q2));
164        assert!(!(q1 >= q2));
165    }
166
167    #[test]
168    fn special_alone() {
169        let system = UnitSystem::<f64>::default();
170        let q1 = system.parse("0 Cel").unwrap();
171        let q2 = system.parse("273.15 K").unwrap();
172        assert_eq_q!(q1, q2);
173    }
174
175    #[test]
176    fn special_combined() {
177        let system = UnitSystem::<f64>::default();
178        let u1 = system.parse("1 Cel/s").unwrap();
179        let u2 = system.parse("1 K/s").unwrap();
180        assert_eq!(u1, u2);
181    }
182
183    #[test]
184    fn special_exponent() {
185        let system = UnitSystem::<f64>::default();
186        let u1 = system.parse("2 Cel2").unwrap();
187        let u2 = system.parse("2 K2").unwrap();
188        assert_eq!(u1, u2);
189    }
190
191    #[test]
192    fn celsius_is_metric() {
193        let system = UnitSystem::<f64>::default();
194        let q1 = system.parse("1 kCel").unwrap();
195        let q2 = system.parse("1273.15 K").unwrap();
196        assert_eq_q!(q1, q2);
197    }
198
199    #[test]
200    fn ten_star() {
201        // check that 10 in 10* is not mistaken for a value
202        let system = UnitSystem::<f64>::default();
203        let q1 = system.parse("10*").unwrap();
204        let q2 = system.parse("10 {dimless}").unwrap();
205        assert_eq_q!(q1, q2);
206    }
207
208    #[test]
209    fn ten_carret() {
210        // check that 10 in 10^ is not mistaken for a value
211        let system = UnitSystem::<f64>::default();
212        let q1 = system.parse("10^").unwrap();
213        let q2 = system.parse("10 {dimless}").unwrap();
214        assert_eq_q!(q1, q2);
215    }
216
217    #[test_case("s", "s")]
218    #[test_case("M", "m")]
219    #[test_case("CM", "cm")]
220    #[test_case("km", "km")]
221    #[test_case("mG", "mg")]
222    #[test_case("Ms", "ms")]
223    #[test_case("mam", "Mm")]
224    #[test_case("HPAL", "hPa")]
225    #[test_case("Sie", "S")]
226    fn case_insensitive(txt1: &str, txt2: &str) {
227        let ci_system = UnitSystemFactory::<f64>::new()
228            .case_sensitive(false)
229            .build();
230        let cs_system = UnitSystem::<f64>::default();
231        let q1 = ci_system.parse(txt1).unwrap();
232        let q2 = cs_system.parse(txt2).unwrap();
233        assert_eq!(q1, q2);
234    }
235}