1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code, missing_docs)]
3#![no_std]
4
5use approx::relative_eq;
6#[allow(unused_imports)]
7#[cfg(feature = "no-std")]
8use micromath::F32Ext;
9#[cfg(not(feature = "no-std"))]
10extern crate std;
11
12pub mod unit;
14
15#[derive(Clone, Copy, Debug, Default)]
17pub struct Temperature<U: unit::TemperatureUnit> {
18 pub(crate) value: U,
19}
20
21impl<U: unit::TemperatureUnit> Temperature<U> {
22 pub fn celsius(&self) -> f32 {
24 self.value.celsius()
25 }
26
27 pub fn fahrenheit(&self) -> f32 {
29 self.value.fahrenheit()
30 }
31}
32
33impl<U: unit::TemperatureUnit> PartialEq for Temperature<U> {
34 fn eq(&self, other: &Self) -> bool {
35 relative_eq!(self.celsius(), &other.celsius(), epsilon = 0.01)
36 }
37}
38
39impl Temperature<unit::Celsius> {
40 pub fn new(value: f32) -> Temperature<unit::Celsius> {
42 Temperature {
43 value: unit::Celsius(value),
44 }
45 }
46}
47
48impl From<Temperature<unit::Fahrenheit>> for Temperature<unit::Celsius> {
49 fn from(value: Temperature<unit::Fahrenheit>) -> Self {
50 Self {
51 value: value.value.into(),
52 }
53 }
54}
55
56impl Temperature<unit::Fahrenheit> {
57 pub fn new(value: f32) -> Temperature<unit::Fahrenheit> {
59 Temperature {
60 value: unit::Fahrenheit(value),
61 }
62 }
63}
64
65impl From<Temperature<unit::Celsius>> for Temperature<unit::Fahrenheit> {
66 fn from(value: Temperature<unit::Celsius>) -> Self {
67 Self {
68 value: value.value.into(),
69 }
70 }
71}
72
73pub type RelativeHumidity = f32;
75
76pub type AbsoluteHumidity = f32;
78
79pub type BarometricPressure = f32;
81
82pub type Altitude = f32;
84
85#[derive(Clone, Copy, Debug, Default)]
87pub struct TemperatureAndRelativeHumidity<U: unit::TemperatureUnit> {
88 pub relative_humidity: RelativeHumidity,
90 pub temperature: Temperature<U>,
92}
93
94fn calculate_absolute_humidity(temperature: f32, relative_humidity: f32) -> f32 {
95 (6.112 * ((17.67 * temperature) / (temperature + 243.5)).exp() * relative_humidity * 2.1674)
96 / (273.15 + temperature)
97}
98
99impl<U: unit::TemperatureUnit> TemperatureAndRelativeHumidity<U> {
100 pub fn absolute_humidity(&self) -> AbsoluteHumidity {
102 calculate_absolute_humidity(self.temperature.celsius(), self.relative_humidity)
103 }
104}
105
106impl<U: unit::TemperatureUnit> PartialEq for TemperatureAndRelativeHumidity<U> {
107 fn eq(&self, other: &Self) -> bool {
108 relative_eq!(self.relative_humidity, other.relative_humidity)
109 && self.temperature.eq(&other.temperature)
110 }
111}
112
113impl TemperatureAndRelativeHumidity<unit::Celsius> {
114 pub fn new(
116 temperature: f32,
117 relative_humidity: f32,
118 ) -> TemperatureAndRelativeHumidity<unit::Celsius> {
119 TemperatureAndRelativeHumidity {
120 relative_humidity,
121 temperature: Temperature::<unit::Celsius>::new(temperature),
122 }
123 }
124}
125
126impl From<TemperatureAndRelativeHumidity<unit::Fahrenheit>>
127 for TemperatureAndRelativeHumidity<unit::Celsius>
128{
129 fn from(value: TemperatureAndRelativeHumidity<unit::Fahrenheit>) -> Self {
130 Self::new(value.temperature.celsius(), value.relative_humidity)
131 }
132}
133
134impl TemperatureAndRelativeHumidity<unit::Fahrenheit> {
135 pub fn new(
137 temperature: f32,
138 relative_humidity: f32,
139 ) -> TemperatureAndRelativeHumidity<unit::Fahrenheit> {
140 TemperatureAndRelativeHumidity {
141 relative_humidity,
142 temperature: Temperature::<unit::Fahrenheit>::new(temperature),
143 }
144 }
145}
146
147impl From<TemperatureAndRelativeHumidity<unit::Celsius>>
148 for TemperatureAndRelativeHumidity<unit::Fahrenheit>
149{
150 fn from(value: TemperatureAndRelativeHumidity<unit::Celsius>) -> Self {
151 Self::new(value.temperature.fahrenheit(), value.relative_humidity)
152 }
153}
154
155#[derive(Clone, Copy, Debug, Default, PartialEq)]
157pub struct TemperatureAndBarometricPressure<U: unit::TemperatureUnit> {
158 pub barometric_pressure: BarometricPressure,
160 pub temperature: Temperature<U>,
162}
163
164fn calculate_altitude(temperature: f32, barometric_pressure: f32) -> f32 {
165 ((1_013.25 / barometric_pressure).powf(1.0 / 5.257) - 1.0) * (temperature + 273.15) / 0.0065
166}
167
168impl<U: unit::TemperatureUnit> TemperatureAndBarometricPressure<U> {
169 pub fn altitude(&self) -> Altitude {
171 calculate_altitude(self.temperature.celsius(), self.barometric_pressure)
172 }
173}
174
175impl TemperatureAndBarometricPressure<unit::Celsius> {
176 pub fn new(
178 temperature: f32,
179 barometric_pressure: f32,
180 ) -> TemperatureAndBarometricPressure<unit::Celsius> {
181 TemperatureAndBarometricPressure {
182 barometric_pressure,
183 temperature: Temperature::<unit::Celsius>::new(temperature),
184 }
185 }
186}
187
188impl From<TemperatureAndBarometricPressure<unit::Fahrenheit>>
189 for TemperatureAndBarometricPressure<unit::Celsius>
190{
191 fn from(value: TemperatureAndBarometricPressure<unit::Fahrenheit>) -> Self {
192 Self::new(value.temperature.celsius(), value.barometric_pressure)
193 }
194}
195
196impl TemperatureAndBarometricPressure<unit::Fahrenheit> {
197 pub fn new(
199 temperature: f32,
200 barometric_pressure: f32,
201 ) -> TemperatureAndBarometricPressure<unit::Fahrenheit> {
202 TemperatureAndBarometricPressure {
203 barometric_pressure,
204 temperature: Temperature::<unit::Fahrenheit>::new(temperature),
205 }
206 }
207}
208
209impl From<TemperatureAndBarometricPressure<unit::Celsius>>
210 for TemperatureAndBarometricPressure<unit::Fahrenheit>
211{
212 fn from(value: TemperatureAndBarometricPressure<unit::Celsius>) -> Self {
213 Self::new(value.temperature.fahrenheit(), value.barometric_pressure)
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::unit::{Celsius, Fahrenheit, TemperatureUnit};
221 use approx::assert_relative_eq;
222 use rstest::rstest;
223
224 #[rstest]
225 #[case(TemperatureAndRelativeHumidity::<Celsius>::new(21.18, 45.59), 8.43)]
226 #[case(TemperatureAndRelativeHumidity::<Fahrenheit>::new(70.12, 45.59), 8.43)]
227 #[case(TemperatureAndRelativeHumidity::<Celsius>::new(2.93, 34.71), 2.06)]
228 #[case(TemperatureAndRelativeHumidity::<Fahrenheit>::new(107.7, 74.91), 42.49)]
229 fn test_absolute_humidity_computation<U: TemperatureUnit>(
230 #[case] input: TemperatureAndRelativeHumidity<U>,
231 #[case] expected_absolute_humidity: AbsoluteHumidity,
232 ) {
233 assert_relative_eq!(
234 input.absolute_humidity(),
235 expected_absolute_humidity,
236 epsilon = 0.01
237 );
238 }
239
240 #[rstest]
241 #[case(TemperatureAndBarometricPressure::<Celsius>::new(20.55, 991.32), 188.46)]
242 #[case(TemperatureAndBarometricPressure::<Celsius>::new(17.93, 1013.25), 0.0)]
243 #[case(TemperatureAndBarometricPressure::<Celsius>::new(37.5, 1013.25), 0.0)]
244 #[case(TemperatureAndBarometricPressure::<Celsius>::new(19.37, 962.81), 439.25)]
245 #[case(TemperatureAndBarometricPressure::<Fahrenheit>::new(99.5, 1013.25), 0.0)]
246 fn test_altitude_computation<U: TemperatureUnit>(
247 #[case] input: TemperatureAndBarometricPressure<U>,
248 #[case] expected_altitude: Altitude,
249 ) {
250 assert_relative_eq!(input.altitude(), expected_altitude, epsilon = 0.01);
251 }
252
253 #[rstest]
254 #[case(0.0, 32.0)]
255 #[case(15.73, 60.31)]
256 #[case(-7.49, 18.52)]
257 #[case(37.5, 99.5)]
258 fn test_celsius_to_fahrenheit_temperature_conversion(
259 #[case] input: f32,
260 #[case] expected_fahrenheit: f32,
261 ) {
262 let temperature: Temperature<Fahrenheit> = Temperature::<Celsius>::new(input).into();
263 assert_relative_eq!(temperature.value.0, expected_fahrenheit, epsilon = 0.01);
264 assert_relative_eq!(
265 temperature.fahrenheit(),
266 expected_fahrenheit,
267 epsilon = 0.01
268 );
269 assert_relative_eq!(temperature.celsius(), input, epsilon = 0.01);
270 }
271
272 #[rstest]
273 #[case(32.0, 0.0)]
274 #[case(60.31, 15.73)]
275 #[case(18.52, -7.49)]
276 #[case(99.5, 37.5)]
277 fn test_fahrenheit_to_celsius_temperature_conversion(
278 #[case] input: f32,
279 #[case] expected_celsius: f32,
280 ) {
281 let temperature: Temperature<Celsius> = Temperature::<Fahrenheit>::new(input).into();
282 assert_relative_eq!(temperature.value.0, expected_celsius, epsilon = 0.01);
283 assert_relative_eq!(temperature.celsius(), expected_celsius, epsilon = 0.01);
284 assert_relative_eq!(temperature.fahrenheit(), input, epsilon = 0.01);
285 }
286
287 #[rstest]
288 #[case(TemperatureAndRelativeHumidity::<Celsius>::new(21.18, 45.59), TemperatureAndRelativeHumidity::<Fahrenheit>::new(70.12, 45.59))]
289 #[case(TemperatureAndRelativeHumidity::<Celsius>::new(-7.49, 73.19), TemperatureAndRelativeHumidity::<Fahrenheit>::new(18.52, 73.19))]
290 fn test_temperature_and_relative_humidity_celsius_to_fahrenheit_conversion(
291 #[case] input: TemperatureAndRelativeHumidity<Celsius>,
292 #[case] expected: TemperatureAndRelativeHumidity<Fahrenheit>,
293 ) {
294 let value: TemperatureAndRelativeHumidity<Fahrenheit> = input.into();
295 assert_eq!(value, expected);
296 }
297
298 #[rstest]
299 #[case(TemperatureAndRelativeHumidity::<Fahrenheit>::new(70.12, 45.59), TemperatureAndRelativeHumidity::<Celsius>::new(21.18, 45.59))]
300 #[case(TemperatureAndRelativeHumidity::<Fahrenheit>::new(18.52, 73.19), TemperatureAndRelativeHumidity::<Celsius>::new(-7.49, 73.19))]
301 fn test_temperature_and_relative_humidity_fahrenheit_to_celsius_conversion(
302 #[case] input: TemperatureAndRelativeHumidity<Fahrenheit>,
303 #[case] expected: TemperatureAndRelativeHumidity<Celsius>,
304 ) {
305 let value: TemperatureAndRelativeHumidity<Celsius> = input.into();
306 assert_eq!(value, expected);
307 }
308
309 #[rstest]
310 #[case(TemperatureAndBarometricPressure::<Celsius>::new(21.18, 991.32), TemperatureAndBarometricPressure::<Fahrenheit>::new(70.12, 991.32))]
311 #[case(TemperatureAndBarometricPressure::<Celsius>::new(37.5, 1013.25), TemperatureAndBarometricPressure::<Fahrenheit>::new(99.5, 1013.25))]
312 fn test_temperature_and_barometric_pressure_celsius_to_fahrenheit_conversion(
313 #[case] input: TemperatureAndBarometricPressure<Celsius>,
314 #[case] expected: TemperatureAndBarometricPressure<Fahrenheit>,
315 ) {
316 let value: TemperatureAndBarometricPressure<Fahrenheit> = input.into();
317 assert_eq!(value, expected);
318 }
319
320 #[rstest]
321 #[case(TemperatureAndBarometricPressure::<Fahrenheit>::new(70.12, 991.32), TemperatureAndBarometricPressure::<Celsius>::new(21.18, 991.32))]
322 #[case(TemperatureAndBarometricPressure::<Fahrenheit>::new(99.5, 1013.25), TemperatureAndBarometricPressure::<Celsius>::new(37.5, 1013.25))]
323 fn test_temperature_and_barometric_pressure_fahrenheit_to_celsius_conversion(
324 #[case] input: TemperatureAndBarometricPressure<Fahrenheit>,
325 #[case] expected: TemperatureAndBarometricPressure<Celsius>,
326 ) {
327 let value: TemperatureAndBarometricPressure<Celsius> = input.into();
328 assert_eq!(value, expected);
329 }
330}