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}