Skip to main content

pamoja_kit/
units.rs

1//! Converting readings between real-world units.
2//!
3//! Cheap environmental sensors report whatever unit their datasheet chose - a BME280
4//! gives pressure in pascals, a thermocouple in degrees Celsius - while the person
5//! reading a dashboard thinks in another. These are the exact, named conversions for the
6//! units the cookbook uses most, so a conversion is a call with an obvious name rather
7//! than a magic constant copied into application code.
8
9/// Pascals in one pound per square inch.
10const PASCALS_PER_PSI: f32 = 6894.7573;
11
12/// Converts a temperature from degrees Celsius to degrees Fahrenheit.
13///
14/// # Arguments
15///
16/// * `celsius` - a temperature in degrees Celsius.
17///
18/// # Returns
19///
20/// The temperature in degrees Fahrenheit, `celsius * 9 / 5 + 32`.
21pub fn celsius_to_fahrenheit(celsius: f32) -> f32 {
22    celsius * 9.0 / 5.0 + 32.0
23}
24
25/// Converts a temperature from degrees Fahrenheit to degrees Celsius.
26///
27/// # Arguments
28///
29/// * `fahrenheit` - a temperature in degrees Fahrenheit.
30///
31/// # Returns
32///
33/// The temperature in degrees Celsius, `(fahrenheit - 32) * 5 / 9`.
34pub fn fahrenheit_to_celsius(fahrenheit: f32) -> f32 {
35    (fahrenheit - 32.0) * 5.0 / 9.0
36}
37
38/// Converts a temperature from degrees Celsius to kelvin.
39///
40/// # Arguments
41///
42/// * `celsius` - a temperature in degrees Celsius.
43///
44/// # Returns
45///
46/// The temperature in kelvin, `celsius + 273.15`.
47pub fn celsius_to_kelvin(celsius: f32) -> f32 {
48    celsius + 273.15
49}
50
51/// Converts a temperature from kelvin to degrees Celsius.
52///
53/// # Arguments
54///
55/// * `kelvin` - a temperature in kelvin.
56///
57/// # Returns
58///
59/// The temperature in degrees Celsius, `kelvin - 273.15`.
60pub fn kelvin_to_celsius(kelvin: f32) -> f32 {
61    kelvin - 273.15
62}
63
64/// Converts a pressure from pascals to hectopascals (millibars).
65///
66/// # Arguments
67///
68/// * `pascals` - a pressure in pascals.
69///
70/// # Returns
71///
72/// The pressure in hectopascals, the unit weather reports use, `pascals / 100`.
73pub fn pascals_to_hectopascals(pascals: f32) -> f32 {
74    pascals / 100.0
75}
76
77/// Converts a pressure from hectopascals (millibars) to pascals.
78///
79/// # Arguments
80///
81/// * `hectopascals` - a pressure in hectopascals.
82///
83/// # Returns
84///
85/// The pressure in pascals, `hectopascals * 100`.
86pub fn hectopascals_to_pascals(hectopascals: f32) -> f32 {
87    hectopascals * 100.0
88}
89
90/// Converts a pressure from pascals to kilopascals.
91///
92/// # Arguments
93///
94/// * `pascals` - a pressure in pascals.
95///
96/// # Returns
97///
98/// The pressure in kilopascals, `pascals / 1000`.
99pub fn pascals_to_kilopascals(pascals: f32) -> f32 {
100    pascals / 1000.0
101}
102
103/// Converts a pressure from kilopascals to pascals.
104///
105/// # Arguments
106///
107/// * `kilopascals` - a pressure in kilopascals.
108///
109/// # Returns
110///
111/// The pressure in pascals, `kilopascals * 1000`.
112pub fn kilopascals_to_pascals(kilopascals: f32) -> f32 {
113    kilopascals * 1000.0
114}
115
116/// Converts a pressure from pascals to pounds per square inch.
117///
118/// # Arguments
119///
120/// * `pascals` - a pressure in pascals.
121///
122/// # Returns
123///
124/// The pressure in psi, where one psi is 6894.7573 pascals.
125pub fn pascals_to_psi(pascals: f32) -> f32 {
126    pascals / PASCALS_PER_PSI
127}
128
129/// Converts a pressure from pounds per square inch to pascals.
130///
131/// # Arguments
132///
133/// * `psi` - a pressure in pounds per square inch.
134///
135/// # Returns
136///
137/// The pressure in pascals, where one psi is 6894.7573 pascals.
138pub fn psi_to_pascals(psi: f32) -> f32 {
139    psi * PASCALS_PER_PSI
140}
141
142/// Converts a fraction in `0.0..=1.0` to a percentage.
143///
144/// # Arguments
145///
146/// * `ratio` - a fraction, where `1.0` is the whole.
147///
148/// # Returns
149///
150/// The equivalent percentage, `ratio * 100`.
151pub fn ratio_to_percent(ratio: f32) -> f32 {
152    ratio * 100.0
153}
154
155/// Converts a percentage to a fraction in `0.0..=1.0`.
156///
157/// # Arguments
158///
159/// * `percent` - a percentage, where `100.0` is the whole.
160///
161/// # Returns
162///
163/// The equivalent fraction, `percent / 100`.
164pub fn percent_to_ratio(percent: f32) -> f32 {
165    percent / 100.0
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    fn approx(a: f32, b: f32, tol: f32) -> bool {
173        (a - b).abs() < tol
174    }
175
176    #[test]
177    fn temperature_reference_points() {
178        assert!(approx(celsius_to_fahrenheit(100.0), 212.0, 1e-3));
179        assert!(approx(celsius_to_fahrenheit(0.0), 32.0, 1e-3));
180        assert!(approx(celsius_to_fahrenheit(37.0), 98.6, 1e-2));
181        // -40 is the point where the Celsius and Fahrenheit scales coincide.
182        assert!(approx(celsius_to_fahrenheit(-40.0), -40.0, 1e-3));
183        assert!(approx(fahrenheit_to_celsius(212.0), 100.0, 1e-3));
184        assert!(approx(celsius_to_kelvin(0.0), 273.15, 1e-2));
185        assert!(approx(kelvin_to_celsius(273.15), 0.0, 1e-2));
186    }
187
188    #[test]
189    fn temperature_round_trips() {
190        assert!(approx(
191            fahrenheit_to_celsius(celsius_to_fahrenheit(21.0)),
192            21.0,
193            1e-3
194        ));
195        assert!(approx(
196            kelvin_to_celsius(celsius_to_kelvin(21.0)),
197            21.0,
198            1e-3
199        ));
200    }
201
202    #[test]
203    fn pressure_reference_points() {
204        // One standard atmosphere expressed four ways.
205        assert!(approx(pascals_to_hectopascals(101325.0), 1013.25, 1e-1));
206        assert!(approx(pascals_to_kilopascals(101325.0), 101.325, 1e-2));
207        assert!(approx(pascals_to_psi(101325.0), 14.6959, 1e-2));
208        assert!(approx(hectopascals_to_pascals(1013.25), 101325.0, 1.0));
209        assert!(approx(psi_to_pascals(1.0), 6894.7573, 1e-1));
210    }
211
212    #[test]
213    fn percent_helpers() {
214        assert!(approx(ratio_to_percent(0.25), 25.0, 1e-4));
215        assert!(approx(percent_to_ratio(25.0), 0.25, 1e-4));
216        assert!(approx(percent_to_ratio(ratio_to_percent(0.6)), 0.6, 1e-4));
217    }
218}