Skip to main content

pnc_engine/
unit.rs

1use crate::error::PncError;
2use crate::value::Value;
3use num_bigint::BigInt;
4use num_rational::BigRational;
5use serde::{Deserialize, Serialize};
6use std::fmt;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub enum UnitId {
10    Celsius,
11    Fahrenheit,
12    Kelvin,
13    Unknown(String),
14}
15
16impl fmt::Display for UnitId {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            UnitId::Celsius => write!(f, "c"),
20            UnitId::Fahrenheit => write!(f, "f"),
21            UnitId::Kelvin => write!(f, "k"),
22            UnitId::Unknown(s) => write!(f, "{}", s),
23        }
24    }
25}
26
27fn to_rational(v: &Value) -> Result<BigRational, PncError> {
28    match v {
29        Value::Int(n) => Ok(BigRational::from_integer(n.clone())),
30        Value::Rational(r) => Ok(r.clone()),
31        _ => Err(PncError::InvalidUnitConversion),
32    }
33}
34
35fn kelvin_offset() -> BigRational {
36    BigRational::new(BigInt::from(27315), BigInt::from(100))
37}
38
39fn to_kelvin(value: &Value) -> Result<BigRational, PncError> {
40    match value {
41        Value::Unit { number, unit } => {
42            let n = to_rational(number)?;
43            match unit {
44                UnitId::Celsius => Ok(n + kelvin_offset()),
45                UnitId::Fahrenheit => Ok((n - BigInt::from(32))
46                    * BigRational::new(BigInt::from(5), BigInt::from(9))
47                    + kelvin_offset()),
48                UnitId::Kelvin => Ok(n),
49                _ => Err(PncError::InvalidUnitConversion),
50            }
51        }
52        _ => Err(PncError::InvalidUnitConversion),
53    }
54}
55
56fn rational_to_value(r: BigRational) -> Value {
57    if r.denom() == &BigInt::from(1) {
58        Value::Int(r.numer().clone())
59    } else {
60        Value::Rational(r)
61    }
62}
63
64fn from_kelvin(k: BigRational, target: UnitId) -> Value {
65    match target {
66        UnitId::Celsius => Value::Unit {
67            number: Box::new(rational_to_value(k - kelvin_offset())),
68            unit: target,
69        },
70        UnitId::Fahrenheit => {
71            let f = (k - kelvin_offset()) * BigRational::new(BigInt::from(9), BigInt::from(5))
72                + BigInt::from(32);
73            Value::Unit {
74                number: Box::new(rational_to_value(f)),
75                unit: target,
76            }
77        }
78        UnitId::Kelvin => Value::Unit {
79            number: Box::new(rational_to_value(k)),
80            unit: target,
81        },
82        UnitId::Unknown(_) => Value::Unit {
83            number: Box::new(rational_to_value(k)),
84            unit: target,
85        },
86    }
87}
88
89pub fn convert(value: &Value, target: UnitId) -> Result<Value, PncError> {
90    let k = to_kelvin(value)?;
91    Ok(from_kelvin(k, target))
92}