use std::any::Any;
use std::ops::{Add, Div, Sub};
use crate::physics::basic::{Coef, PhysicalQuantity, Temperature, TemperatureDifference};
const KELVIN_OFFSET: f64 = 273.15;
const FAHRENHEIT_RATIO: f64 = 5.0 / 9.0;
impl Default for Temperature {
fn default() -> Self {
Self::from_kelvin(0.0)
}
}
impl PhysicalQuantity for Temperature {
fn as_any(&self) -> &dyn Any {
self
}
fn is_zero(&self) -> bool {
self.as_kelvin() == 0.0
}
fn default_unit_value(&self) -> f64 {
self.as_kelvin()
}
fn set_value(&mut self, value: f64) {
self.kelvin = value;
}
fn unit_display_name(&self) -> String {
"Temperature (K)".to_string()
}
}
impl Temperature {
pub fn from_kelvin(v: f64) -> Self {
Self { kelvin: v }
}
pub fn from_celsius(v: f64) -> Self {
Self {
kelvin: v + KELVIN_OFFSET,
}
}
pub fn from_fahrenheit(v: f64) -> Self {
Self {
kelvin: (v - 32.0) * FAHRENHEIT_RATIO + KELVIN_OFFSET,
}
}
pub fn from_rankine(v: f64) -> Self {
Self {
kelvin: v * FAHRENHEIT_RATIO,
}
}
pub fn as_kelvin(&self) -> f64 {
self.kelvin
}
pub fn as_celsius(&self) -> f64 {
self.kelvin - KELVIN_OFFSET
}
pub fn as_fahrenheit(&self) -> f64 {
(self.kelvin - KELVIN_OFFSET) / FAHRENHEIT_RATIO + 32.0
}
pub fn as_rankine(&self) -> f64 {
self.kelvin / FAHRENHEIT_RATIO
}
}
impl Sub for Temperature {
type Output = TemperatureDifference;
fn sub(self, rhs: Self) -> Self::Output {
TemperatureDifference::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl<'a, 'b> Sub<&'b Temperature> for &'a Temperature {
type Output = TemperatureDifference;
fn sub(self, rhs: &'b Temperature) -> Self::Output {
TemperatureDifference::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl<'a> Sub<&'a Temperature> for Temperature {
type Output = TemperatureDifference;
fn sub(self, rhs: &'a Temperature) -> Self::Output {
TemperatureDifference::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl<'a> Sub<Temperature> for &'a Temperature {
type Output = TemperatureDifference;
fn sub(self, rhs: Temperature) -> Self::Output {
TemperatureDifference::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl Add<TemperatureDifference> for Temperature {
type Output = Temperature;
fn add(self, rhs: TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() + rhs.as_kelvin())
}
}
impl<'a> Add<TemperatureDifference> for &'a Temperature {
type Output = Temperature;
fn add(self, rhs: TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() + rhs.as_kelvin())
}
}
impl<'a> Add<&'a TemperatureDifference> for Temperature {
type Output = Temperature;
fn add(self, rhs: &'a TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() + rhs.as_kelvin())
}
}
impl<'a, 'b> Add<&'b TemperatureDifference> for &'a Temperature {
type Output = Temperature;
fn add(self, rhs: &'b TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() + rhs.as_kelvin())
}
}
impl Sub<TemperatureDifference> for Temperature {
type Output = Temperature;
fn sub(self, rhs: TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl<'a> Sub<TemperatureDifference> for &'a Temperature {
type Output = Temperature;
fn sub(self, rhs: TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl<'a> Sub<&'a TemperatureDifference> for Temperature {
type Output = Temperature;
fn sub(self, rhs: &'a TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl<'a, 'b> Sub<&'b TemperatureDifference> for &'a Temperature {
type Output = Temperature;
fn sub(self, rhs: &'b TemperatureDifference) -> Self::Output {
Temperature::from_kelvin(self.as_kelvin() - rhs.as_kelvin())
}
}
impl Div for Temperature {
type Output = Coef;
fn div(self, rhs: Self) -> Self::Output {
Coef::new(self.as_kelvin() / rhs.as_kelvin())
}
}
impl<'a, 'b> Div<&'b Temperature> for &'a Temperature {
type Output = Coef;
fn div(self, rhs: &'b Temperature) -> Self::Output {
Coef::new(self.as_kelvin() / rhs.as_kelvin())
}
}
impl<'a> Div<&'a Temperature> for Temperature {
type Output = Coef;
fn div(self, rhs: &'a Temperature) -> Self::Output {
Coef::new(self.as_kelvin() / rhs.as_kelvin())
}
}
impl<'a> Div<Temperature> for &'a Temperature {
type Output = Coef;
fn div(self, rhs: Temperature) -> Self::Output {
Coef::new(self.as_kelvin() / rhs.as_kelvin())
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_construction_and_units() {
let t = Temperature::from_celsius(0.0);
assert_relative_eq!(t.as_kelvin(), 273.15, epsilon = 1e-9);
assert_relative_eq!(t.as_fahrenheit(), 32.0, epsilon = 1e-9);
let t = Temperature::from_celsius(100.0);
assert_relative_eq!(t.as_kelvin(), 373.15, epsilon = 1e-9);
assert_relative_eq!(t.as_fahrenheit(), 212.0, epsilon = 1e-9);
let t = Temperature::from_celsius(-273.15);
assert_relative_eq!(t.as_kelvin(), 0.0, epsilon = 1e-9);
assert_relative_eq!(t.as_rankine(), 0.0, epsilon = 1e-9);
let t = Temperature::from_rankine(491.67);
assert_relative_eq!(t.as_kelvin(), 273.15, epsilon = 1e-9);
assert_relative_eq!(
Temperature::from_kelvin(273.15).as_rankine(),
491.67,
epsilon = 1e-9
);
}
#[test]
fn test_round_trip() {
for v in [-40.0, 0.0, 20.0, 37.0, 100.0] {
let t = Temperature::from_celsius(v);
assert_relative_eq!(t.as_celsius(), v, epsilon = 1e-9);
let t2 = Temperature::from_fahrenheit(t.as_fahrenheit());
assert_relative_eq!(t2.as_celsius(), v, epsilon = 1e-9);
let t3 = Temperature::from_kelvin(t.as_kelvin());
assert_relative_eq!(t3.as_celsius(), v, epsilon = 1e-9);
}
let t = Temperature::from_celsius(-40.0);
assert_relative_eq!(t.as_fahrenheit(), -40.0, epsilon = 1e-9);
}
#[test]
fn test_temperature_minus_temperature() {
let t1 = Temperature::from_celsius(30.0);
let t2 = Temperature::from_celsius(20.0);
let dt = t1 - t2;
assert_relative_eq!(dt.as_kelvin(), 10.0, epsilon = 1e-9);
let t3 = Temperature::from_fahrenheit(50.0); let dt2 = t1 - t3;
assert_relative_eq!(dt2.as_kelvin(), 20.0, epsilon = 1e-9);
let dt3 = t2 - t1;
assert_relative_eq!(dt3.as_kelvin(), -10.0, epsilon = 1e-9);
assert_relative_eq!((&t1 - &t2).as_kelvin(), 10.0, epsilon = 1e-9);
assert_relative_eq!((t1 - &t2).as_kelvin(), 10.0, epsilon = 1e-9);
assert_relative_eq!((&t1 - t2).as_kelvin(), 10.0, epsilon = 1e-9);
}
#[test]
fn test_add_and_sub_difference() {
let t = Temperature::from_celsius(20.0);
let dt = TemperatureDifference::from_kelvin(15.0);
let heated = t + dt;
assert_relative_eq!(heated.as_celsius(), 35.0, epsilon = 1e-9);
let cooled = t - dt;
assert_relative_eq!(cooled.as_celsius(), 5.0, epsilon = 1e-9);
assert_relative_eq!((&t + dt).as_celsius(), 35.0, epsilon = 1e-9);
assert_relative_eq!((t + &dt).as_celsius(), 35.0, epsilon = 1e-9);
assert_relative_eq!((&t + &dt).as_celsius(), 35.0, epsilon = 1e-9);
assert_relative_eq!((&t - dt).as_celsius(), 5.0, epsilon = 1e-9);
assert_relative_eq!((t - &dt).as_celsius(), 5.0, epsilon = 1e-9);
assert_relative_eq!((&t - &dt).as_celsius(), 5.0, epsilon = 1e-9);
assert_relative_eq!(((t + dt) - dt).as_celsius(), 20.0, epsilon = 1e-9);
}
#[test]
fn test_ratio_uses_kelvin() {
let th = Temperature::from_celsius(500.0); let tc = Temperature::from_celsius(27.0);
let ratio = tc / th;
assert_relative_eq!(ratio.get_value(), 300.15 / 773.15, epsilon = 1e-12);
let efficiency = 1.0 - ratio.get_value();
assert!(efficiency > 0.6 && efficiency < 0.62);
assert_ne!(
ratio.get_value(),
27.0 / 500.0,
"温度比必须基于开尔文,摄氏直接相除无效"
);
assert_relative_eq!((&tc / &th).get_value(), ratio.get_value(), epsilon = 1e-12);
assert_relative_eq!((tc / &th).get_value(), ratio.get_value(), epsilon = 1e-12);
assert_relative_eq!((&tc / th).get_value(), ratio.get_value(), epsilon = 1e-12);
}
#[test]
fn test_is_zero_only_at_absolute_zero() {
assert!(Temperature::from_kelvin(0.0).is_zero());
assert!(!Temperature::from_celsius(0.0).is_zero());
assert!(!Temperature::from_fahrenheit(0.0).is_zero());
}
#[test]
fn test_physical_quantity_contract() {
let mut t = Temperature::default();
assert_relative_eq!(t.as_kelvin(), 0.0, epsilon = 1e-12);
t.set_value(300.0);
assert_relative_eq!(t.default_unit_value(), 300.0, epsilon = 1e-12);
assert_relative_eq!(t.as_kelvin(), 300.0, epsilon = 1e-12);
assert_relative_eq!(t.as_celsius(), 26.85, epsilon = 1e-9);
assert_eq!(t.unit_display_name(), "Temperature (K)");
let any = t.as_any();
assert!(any.downcast_ref::<Temperature>().is_some());
}
#[test]
fn test_ordering() {
let cold = Temperature::from_celsius(-10.0);
let warm = Temperature::from_celsius(30.0);
assert!(cold < warm);
assert!(warm > cold);
assert_eq!(
Temperature::from_celsius(20.0),
Temperature::from_celsius(20.0)
);
}
}