paholg / dimensioned

Compile-time dimensional analysis for various unit systems using Rust's type system.
https://crates.io/crates/dimensioned
MIT License
300 stars 23 forks source link

Feature request: Celsius #66

Closed wellcaffeinated closed 3 years ago

wellcaffeinated commented 5 years ago

I realize that the documentation for ucum says:

There are a few classifications of units that UCUM defines but that it does not make sense to define here, and so we do not. … Units that require conversions that involve more than multiplication. These include some temperature units (such as degrees Celcius)

But easy conversion between Celsius and Kelvin is something I expected to be able to do. Perhaps it doesn’t make sense to implement it in the ucum unit system, but perhaps having an additional unit system would make sense. (maybe it should be called “misc”?).

Here is my pathetic attempt to implement this (which doesn’t compile).

mod ucum_plus {
  make_units! {
    UCUM_PLUS;
    ONE: Unitless;

    base {
      DEGC: DegreeCelsius, "°C", Temperature;
    }
    derived {
    }
    constants {
    }
    fmt = true;
  }

  pub use self::f64consts::*;

  use dim::typenum::{Integer, Z0};
  use std::convert::From;
  use dim::ucum;

  impl<V, Temperature> From<ucum::UCUM<V, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]>>
    for UCUM_PLUS<<<<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output as dim::typenum::Pow<i32>>::Output, tarr![Temperature]>
    where
      V: Mul<f64> + dim::typenum::Pow<i32>,
      Temperature: Integer,
      <V as dim::typenum::Pow<i32>>::Output : Sub<f64>,
      <<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output : dim::typenum::Pow<i32>
  {
    fn from(other: ucum::UCUM<V, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]>) -> Self {
      let pow = Temperature::to_i32();

      let kelvin = other.value_unsafe.powi(-pow);
      let celcius = kelvin - 273.15;

      UCUM_PLUS::new( celcius.powi(pow) )
    }
  }

  impl<V, Temperature> Into<ucum::UCUM<<<<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output as dim::typenum::Pow<i32>>::Output, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]>>
    for UCUM_PLUS<V, tarr![Temperature]>
    where
      V: Mul<f64> + dim::typenum::Pow<i32>,
      Temperature: Integer,
      <V as dim::typenum::Pow<i32>>::Output : Add<f64>,
      <<V as dim::typenum::Pow<i32>>::Output as std::ops::Add<f64>>::Output : dim::typenum::Pow<i32>
  {
    fn into(self) -> ucum::UCUM<<<<V as dim::typenum::Pow<i32>>::Output as std::ops::Sub<f64>>::Output as dim::typenum::Pow<i32>>::Output, tarr![Z0, Z0, Z0, Z0, Temperature, Z0, Z0]> {
      let pow = Temperature::to_i32();

      let celsius = self.value_unsafe.powi(-pow);
      let kelvin = celsius + 273.15;

      ucum::UCUM::new( kelvin.powi(pow) )
    }
  }

  #[cfg(test)]
  mod tests {
    use super::*;

    #[test]
    fn conversion_test() {
      let c = 30. * DEGC;
      let k = 400. * ucum::K;
      let diff = k - c;

      assert_eq!(*(diff/K), 96.85);
    }
  }
}
droundy commented 5 years ago

The problem and challenge with handling Celsius is that you then need two Celsius types, one for temperature differences and one for absolute temperatures, and subtracting two temperatures Celsius must give a Celsius difference. Otherwise you can get nonsense results when you convert to Kelvin.

wellcaffeinated commented 5 years ago

Ah i see what you mean. And i guess that would also necessitate keeping track of kelvin differences in order to convert between those. Fair enough

wellcaffeinated commented 5 years ago

Perhaps adding a constant called: C0 or something similar would make sense then

let twenty_c : ucum::Kelvin<f64> = 20.0 * ucum::C0;
assert_eq!( *(twenty_c/ucum:K), 273.15);

it would have to perform an addition which would be a bit odd though…

paho-outreach commented 5 years ago

I think a constant for 0 Celsius seems reasonable. You would be able to add it, but it would be in Kelvin at that point, so that doesn't seem too bad.

You could also create a function that inputs Kelvin and outputs a (unitless) conversion to Celsius for printing.

That is how I would handle it if I wanted to use Celsius and also use units.

danieleades commented 4 years ago

I think a constant for 0 Celsius seems reasonable. You would be able to add it, but it would be in Kelvin at that point, so that doesn't seem too bad.

You could also create a function that inputs Kelvin and outputs a (unitless) conversion to Celsius for printing.

That is how I would handle it if I wanted to use Celsius and also use units.

that sounds like an extension trait. no reason that needs to exist in the crate