qtty_core/units/temperature.rs
1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Thermodynamic temperature units.
5//!
6//! The canonical scaling unit for this dimension is the **kelvin**
7//! (`Kelvin::RATIO == 1.0`). Temperature here is the SI base dimension Θ
8//! (thermodynamic temperature), measured on an absolute scale.
9//!
10//! Only the kelvin is provided. Affine-offset scales such as Celsius and
11//! Fahrenheit are intentionally omitted: they require an additive offset that
12//! does not compose linearly with other quantities and would silently break
13//! the [`Quantity`] arithmetic guarantees. Convert affine-offset readings to
14//! kelvin at the boundary before constructing a [`Kelvins`] value.
15//!
16//! ```rust
17//! use qtty_core::temperature::Kelvins;
18//!
19//! let t = Kelvins::new(284.65); // ≈ 11.5 °C
20//! assert!((t.value() - 284.65).abs() < 1e-9);
21//! ```
22
23use crate::{Quantity, Unit};
24use qtty_derive::Unit;
25
26/// Re-export the temperature dimension from the dimension module.
27pub use crate::dimension::Temperature;
28
29/// Marker trait for any [`Unit`] whose dimension is [`Temperature`].
30pub trait TemperatureUnit: Unit<Dim = Temperature> {}
31impl<T: Unit<Dim = Temperature>> TemperatureUnit for T {}
32
33// ─────────────────────────────────────────────────────────────────────────────
34// SI kelvin
35// ─────────────────────────────────────────────────────────────────────────────
36
37/// Kelvin — SI base unit of thermodynamic temperature.
38///
39/// BIPM SI brochure 9th ed., Table 2: the kelvin (K) is defined by fixing the
40/// numerical value of the Boltzmann constant *k* to 1.380 649 × 10⁻²³ J K⁻¹.
41#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
42#[unit(symbol = "K", dimension = Temperature, ratio = 1.0)]
43pub struct Kelvin;
44/// Type alias shorthand for [`Kelvin`].
45pub type K = Kelvin;
46/// A quantity measured in kelvin.
47pub type Kelvins = Quantity<Kelvin>;
48/// One kelvin.
49pub const KELVIN: Kelvins = Kelvins::new(1.0);
50
51/// Rankine (°R) — absolute temperature scale; 1 °R = 5/9 K exactly.
52///
53/// The Rankine degree equals one Fahrenheit degree, starting from absolute
54/// zero. Used primarily in US engineering. The conversion to kelvin is
55/// exact: `T_K = T_R × 5/9`.
56#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
57#[unit(symbol = "°R", dimension = Temperature, ratio = 5.0 / 9.0)]
58pub struct Rankine;
59/// Type alias shorthand for [`Rankine`].
60pub type R = Rankine;
61/// A quantity measured in rankine.
62pub type Rankines = Quantity<Rankine>;
63/// One rankine.
64pub const RANKINE: Rankines = Rankines::new(1.0);
65
66// ─────────────────────────────────────────────────────────────────────────────
67// Unit inventory macro
68// ─────────────────────────────────────────────────────────────────────────────
69
70/// Canonical list of always-available temperature units.
71#[macro_export]
72#[doc(hidden)]
73macro_rules! temperature_units {
74 ($cb:path) => {
75 $cb!(Kelvin, Rankine);
76 };
77}
78
79// Generate bidirectional From impls between all temperature units.
80temperature_units!(crate::impl_unit_from_conversions);
81
82#[cfg(feature = "cross-unit-ops")]
83temperature_units!(crate::impl_unit_cross_unit_ops);
84
85// Compile-time check: every temperature unit is registered as BuiltinUnit.
86#[cfg(test)]
87temperature_units!(crate::assert_units_are_builtin);
88
89#[cfg(all(test, feature = "std"))]
90mod tests {
91 use super::*;
92 use approx::assert_abs_diff_eq;
93
94 #[test]
95 fn kelvin_roundtrip() {
96 let t = Kelvins::new(284.65);
97 let t2: Kelvins = t.to();
98 assert_abs_diff_eq!(t2.value(), 284.65, epsilon = 1e-12);
99 }
100
101 #[test]
102 fn kelvin_addition() {
103 let a = Kelvins::new(100.0);
104 let b = Kelvins::new(184.65);
105 let c = a + b;
106 assert_abs_diff_eq!(c.value(), 284.65, epsilon = 1e-12);
107 }
108
109 #[test]
110 fn rankine_to_kelvin() {
111 // 1 °R = 5/9 K exactly
112 let r = Rankines::new(1.0);
113 let k: Kelvins = r.to();
114 assert_abs_diff_eq!(k.value(), 5.0 / 9.0, epsilon = 1e-15);
115 }
116
117 #[test]
118 fn rankine_absolute_zero() {
119 // 0 °R = 0 K
120 let r = Rankines::new(0.0);
121 let k: Kelvins = r.to();
122 assert_abs_diff_eq!(k.value(), 0.0, epsilon = 1e-15);
123 }
124
125 #[test]
126 fn kelvin_to_rankine() {
127 // 273.15 K = 491.67 °R
128 let k = Kelvins::new(273.15);
129 let r: Rankines = k.to();
130 assert_abs_diff_eq!(r.value(), 491.67, epsilon = 1e-9);
131 }
132}