1#![no_std]
2#[cfg(feature = "std")]
9extern crate std;
10
11macro_rules! impl_temperature_conversions {
12 ($ty:ty, $mod_name:ident) => {
13 #[doc = concat!("Temperature conversions for [`", stringify!($ty), "`].")]
14 pub mod $mod_name {
15 #[cfg(feature = "std")]
16 #[inline]
17 const fn fma(a: $ty, b: $ty, c: $ty) -> $ty {
18 <$ty>::mul_add(a, b, c)
19 }
20
21 #[cfg(not(feature = "std"))]
22 #[inline]
23 fn fma(a: $ty, b: $ty, c: $ty) -> $ty {
24 use num_traits::real::Real;
25 Real::mul_add(a, b, c)
26 }
27
28 #[must_use]
29 pub const fn kelvin_to_celsius(val: $ty) -> $ty {
30 val - 273.15
31 }
32
33 #[must_use]
34 pub fn kelvin_to_fahrenheit(val: $ty) -> $ty {
35 fma(val - 273.15, 9. / 5., 32.)
36 }
37
38 #[must_use]
39 pub const fn celsius_to_kelvin(val: $ty) -> $ty {
40 val + 273.15
41 }
42
43 #[must_use]
44 pub fn celsius_to_fahrenheit(val: $ty) -> $ty {
45 fma(val, 9. / 5., 32.)
46 }
47
48 #[must_use]
49 pub fn fahrenheit_to_kelvin(val: $ty) -> $ty {
50 fma(val - 32., 5. / 9., 273.15)
51 }
52
53 #[must_use]
54 pub const fn fahrenheit_to_celsius(val: $ty) -> $ty {
55 (val - 32.) * (5. / 9.)
56 }
57 }
58 };
59}
60
61impl_temperature_conversions!(f64, f64);
62impl_temperature_conversions!(f32, f32);
63
64pub use crate::f64::*;
66
67#[cfg(test)]
68mod temperature_tests {
69 use super::*;
70 use rstest::rstest;
71
72 const PRECISION: f64 = f64::EPSILON;
73
74 fn approx_equal(x: f64, y: f64) -> bool {
76 (x - y).abs() < PRECISION
77 }
78
79 #[rstest]
80 #[case(0.0, -273.15)]
81 #[case(100.0, -173.14999999999998)]
82 #[case(1000.0, 726.85)]
83 #[case(5000.0, 4726.85)]
84 fn test_kelvin_to_celsius_precision(#[case] kelvin_val: f64, #[case] celsius_val: f64) {
85 let converted = kelvin_to_celsius(kelvin_val);
86 assert!(
87 approx_equal(converted, celsius_val),
88 "High precision K->C failed - Expected: {celsius_val}, Got: {converted}"
89 );
90 }
91
92 #[rstest]
93 #[case(0.0, -459.66999999999996)]
94 #[case(100.0, -279.66999999999996)]
95 #[case(1000.0, 1340.3300000000002)]
96 #[case(5000.0, 8540.330000000002)]
97 #[case(69420.0, 124496.33000000002)]
98 fn test_kelvin_to_fahrenheit_precision(#[case] kelvin_val: f64, #[case] fahrenheit_val: f64) {
99 let converted = kelvin_to_fahrenheit(kelvin_val);
100 assert!(
101 approx_equal(converted, fahrenheit_val),
102 "High precision K->F failed - Expected: {fahrenheit_val}, Got: {converted}"
103 );
104 }
105
106 #[rstest]
107 #[case(0.0, 273.15)]
108 #[case(100.0, 373.15)]
109 #[case(1000.0, 1273.15)]
110 #[case(5000.0, 5273.15)]
111 #[case(69420.0, 69693.15)]
112 fn test_celsius_to_kelvin_precision(#[case] celsius_val: f64, #[case] kelvin_val: f64) {
113 let converted = celsius_to_kelvin(celsius_val);
114 assert!(
115 approx_equal(converted, kelvin_val),
116 "High precision C->K failed - Expected: {kelvin_val}, Got: {converted}"
117 );
118 }
119
120 #[rstest]
121 #[case(0.0, 32.0)]
122 #[case(100.0, 212.0)]
123 #[case(1000.0, 1832.0)]
124 #[case(5000.0, 9032.0)]
125 #[case(69420.0, 124988.0)]
126 fn test_celsius_to_fahrenheit_precision(#[case] celsius_val: f64, #[case] fahrenheit_val: f64) {
127 let converted = celsius_to_fahrenheit(celsius_val);
128 assert!(
129 approx_equal(converted, fahrenheit_val),
130 "High precision C->F failed - Expected: {fahrenheit_val}, Got: {converted}"
131 );
132 }
133
134 #[rstest]
135 #[case(0.0, 255.3722222222222)]
136 #[case(100.0, 310.92777777777775)]
137 #[case(1000.0, 810.9277777777778)]
138 #[case(5000.0, 3033.15)]
139 #[case(69420.0, 38822.03888888889)]
140 fn test_fahrenheit_to_kelvin_precision(#[case] fahrenheit_val: f64, #[case] kelvin_val: f64) {
141 let converted = fahrenheit_to_kelvin(fahrenheit_val);
142 assert!(
143 approx_equal(converted, kelvin_val),
144 "High precision F->K failed - Expected: {kelvin_val}, Got: {converted}"
145 );
146 }
147
148 #[rstest]
149 #[case(0.0, -17.77777777777778)]
150 #[case(100.0, 37.77777777777778)]
151 #[case(1000.0, 537.7777777777778)]
152 #[case(5000.0, 2760.0)]
153 #[case(69420.0, 38548.88888888889)]
154 fn test_fahrenheit_to_celsius_precision(#[case] fahrenheit_val: f64, #[case] celsius_val: f64) {
155 let converted = fahrenheit_to_celsius(fahrenheit_val);
156 assert!(
157 approx_equal(converted, celsius_val),
158 "High precision F->C failed - Expected: {celsius_val}, Got: {converted}"
159 );
160 }
161}
162
163#[cfg(test)]
164mod round_trip_tests {
165 macro_rules! round_trip_tests {
166 ($ty:ty, $mod_name:ident) => {
167 mod $mod_name {
168 use crate::$mod_name;
169
170 fn approx_eq(a: $ty, b: $ty) -> bool {
171 const INTRINSIC_SCALE: $ty = 100.; let scale = a.abs().max(b.abs()).max(INTRINSIC_SCALE);
173 (a - b).abs() < <$ty>::EPSILON * scale * 8.0
174 }
175
176 const SAMPLES: [$ty; 5] = [0.0, 100.0, 273.15, 1000.0, 5000.0];
177
178 #[test]
179 fn kelvin_celsius_round_trip() {
180 for &k in &SAMPLES {
181 let back = $mod_name::celsius_to_kelvin($mod_name::kelvin_to_celsius(k));
182 assert!(approx_eq(back, k), "K->C->K failed for {k}: got {back}");
183 }
184 }
185
186 #[test]
187 fn celsius_fahrenheit_round_trip() {
188 for &c in &SAMPLES {
189 let back =
190 $mod_name::fahrenheit_to_celsius($mod_name::celsius_to_fahrenheit(c));
191 assert!(approx_eq(back, c), "C->F->C failed for {c}: got {back}");
192 }
193 }
194
195 #[test]
196 fn kelvin_fahrenheit_round_trip() {
197 for &k in &SAMPLES {
198 let back =
199 $mod_name::fahrenheit_to_kelvin($mod_name::kelvin_to_fahrenheit(k));
200 assert!(approx_eq(back, k), "K->F->K failed for {k}: got {back}");
201 }
202 }
203 }
204 };
205 }
206
207 round_trip_tests!(f64, f64);
208 round_trip_tests!(f32, f32);
209}