#![no_std]
#[cfg(feature = "std")]
extern crate std;
macro_rules! impl_temperature_conversions {
($ty:ty, $mod_name:ident) => {
#[doc = concat!("Temperature conversions for [`", stringify!($ty), "`].")]
pub mod $mod_name {
#[cfg(feature = "std")]
#[inline]
const fn fma(a: $ty, b: $ty, c: $ty) -> $ty {
<$ty>::mul_add(a, b, c)
}
#[cfg(not(feature = "std"))]
#[inline]
fn fma(a: $ty, b: $ty, c: $ty) -> $ty {
use num_traits::real::Real;
Real::mul_add(a, b, c)
}
#[must_use]
pub const fn kelvin_to_celsius(val: $ty) -> $ty {
val - 273.15
}
#[must_use]
pub fn kelvin_to_fahrenheit(val: $ty) -> $ty {
fma(val - 273.15, 9. / 5., 32.)
}
#[must_use]
pub const fn celsius_to_kelvin(val: $ty) -> $ty {
val + 273.15
}
#[must_use]
pub fn celsius_to_fahrenheit(val: $ty) -> $ty {
fma(val, 9. / 5., 32.)
}
#[must_use]
pub fn fahrenheit_to_kelvin(val: $ty) -> $ty {
fma(val - 32., 5. / 9., 273.15)
}
#[must_use]
pub const fn fahrenheit_to_celsius(val: $ty) -> $ty {
(val - 32.) * (5. / 9.)
}
}
};
}
impl_temperature_conversions!(f64, f64);
impl_temperature_conversions!(f32, f32);
pub use crate::f64::*;
#[cfg(test)]
mod temperature_tests {
use super::*;
use rstest::rstest;
const PRECISION: f64 = f64::EPSILON;
fn approx_equal(x: f64, y: f64) -> bool {
(x - y).abs() < PRECISION
}
#[rstest]
#[case(0.0, -273.15)]
#[case(100.0, -173.14999999999998)]
#[case(1000.0, 726.85)]
#[case(5000.0, 4726.85)]
fn test_kelvin_to_celsius_precision(#[case] kelvin_val: f64, #[case] celsius_val: f64) {
let converted = kelvin_to_celsius(kelvin_val);
assert!(
approx_equal(converted, celsius_val),
"High precision K->C failed - Expected: {celsius_val}, Got: {converted}"
);
}
#[rstest]
#[case(0.0, -459.66999999999996)]
#[case(100.0, -279.66999999999996)]
#[case(1000.0, 1340.3300000000002)]
#[case(5000.0, 8540.330000000002)]
#[case(69420.0, 124496.33000000002)]
fn test_kelvin_to_fahrenheit_precision(#[case] kelvin_val: f64, #[case] fahrenheit_val: f64) {
let converted = kelvin_to_fahrenheit(kelvin_val);
assert!(
approx_equal(converted, fahrenheit_val),
"High precision K->F failed - Expected: {fahrenheit_val}, Got: {converted}"
);
}
#[rstest]
#[case(0.0, 273.15)]
#[case(100.0, 373.15)]
#[case(1000.0, 1273.15)]
#[case(5000.0, 5273.15)]
#[case(69420.0, 69693.15)]
fn test_celsius_to_kelvin_precision(#[case] celsius_val: f64, #[case] kelvin_val: f64) {
let converted = celsius_to_kelvin(celsius_val);
assert!(
approx_equal(converted, kelvin_val),
"High precision C->K failed - Expected: {kelvin_val}, Got: {converted}"
);
}
#[rstest]
#[case(0.0, 32.0)]
#[case(100.0, 212.0)]
#[case(1000.0, 1832.0)]
#[case(5000.0, 9032.0)]
#[case(69420.0, 124988.0)]
fn test_celsius_to_fahrenheit_precision(#[case] celsius_val: f64, #[case] fahrenheit_val: f64) {
let converted = celsius_to_fahrenheit(celsius_val);
assert!(
approx_equal(converted, fahrenheit_val),
"High precision C->F failed - Expected: {fahrenheit_val}, Got: {converted}"
);
}
#[rstest]
#[case(0.0, 255.3722222222222)]
#[case(100.0, 310.92777777777775)]
#[case(1000.0, 810.9277777777778)]
#[case(5000.0, 3033.15)]
#[case(69420.0, 38822.03888888889)]
fn test_fahrenheit_to_kelvin_precision(#[case] fahrenheit_val: f64, #[case] kelvin_val: f64) {
let converted = fahrenheit_to_kelvin(fahrenheit_val);
assert!(
approx_equal(converted, kelvin_val),
"High precision F->K failed - Expected: {kelvin_val}, Got: {converted}"
);
}
#[rstest]
#[case(0.0, -17.77777777777778)]
#[case(100.0, 37.77777777777778)]
#[case(1000.0, 537.7777777777778)]
#[case(5000.0, 2760.0)]
#[case(69420.0, 38548.88888888889)]
fn test_fahrenheit_to_celsius_precision(#[case] fahrenheit_val: f64, #[case] celsius_val: f64) {
let converted = fahrenheit_to_celsius(fahrenheit_val);
assert!(
approx_equal(converted, celsius_val),
"High precision F->C failed - Expected: {celsius_val}, Got: {converted}"
);
}
}
#[cfg(test)]
mod round_trip_tests {
macro_rules! round_trip_tests {
($ty:ty, $mod_name:ident) => {
mod $mod_name {
use crate::$mod_name;
fn approx_eq(a: $ty, b: $ty) -> bool {
const INTRINSIC_SCALE: $ty = 100.; let scale = a.abs().max(b.abs()).max(INTRINSIC_SCALE);
(a - b).abs() < <$ty>::EPSILON * scale * 8.0
}
const SAMPLES: [$ty; 5] = [0.0, 100.0, 273.15, 1000.0, 5000.0];
#[test]
fn kelvin_celsius_round_trip() {
for &k in &SAMPLES {
let back = $mod_name::celsius_to_kelvin($mod_name::kelvin_to_celsius(k));
assert!(approx_eq(back, k), "K->C->K failed for {k}: got {back}");
}
}
#[test]
fn celsius_fahrenheit_round_trip() {
for &c in &SAMPLES {
let back =
$mod_name::fahrenheit_to_celsius($mod_name::celsius_to_fahrenheit(c));
assert!(approx_eq(back, c), "C->F->C failed for {c}: got {back}");
}
}
#[test]
fn kelvin_fahrenheit_round_trip() {
for &k in &SAMPLES {
let back =
$mod_name::fahrenheit_to_kelvin($mod_name::kelvin_to_fahrenheit(k));
assert!(approx_eq(back, k), "K->F->K failed for {k}: got {back}");
}
}
}
};
}
round_trip_tests!(f64, f64);
round_trip_tests!(f32, f32);
}