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}