#![doc = include_str!("../DOCS.md")]
pub mod units;
pub struct DimensionalVariable {
pub value: f64,
pub unit: [f64; units::BASE_UNITS_SIZE],
}
impl DimensionalVariable {
pub fn new(value: f64, unit_str: &str) -> Result<Self, String> {
let (base_unit, conversion_factor) = unit_str_to_base_unit(unit_str)
.map_err(|e| format!("Failed to parse unit '{}': {}", unit_str, e))?;
return Ok(DimensionalVariable {
value: value * conversion_factor,
unit: base_unit,
});
}
pub fn value(&self) -> f64 {
self.value
}
pub fn value_in(&self, unit_str: &str) -> Result<f64, String> {
let (unit, conversion_factor) = unit_str_to_base_unit(unit_str)
.map_err(|e| format!("Failed to parse unit '{}': {}", unit_str, e))?;
if !self.check_compatibility(unit) {
return Err(format!("Incompatible unit conversion to: {}", unit_str));
}
return Ok(self.value / conversion_factor);
}
pub fn check_compatibility(&self, other_unit: [f64; units::BASE_UNITS_SIZE]) -> bool {
if self.unit[7] == 1.0 {
return &self.unit[..units::BASE_UNITS_SIZE - 1] == &other_unit[..units::BASE_UNITS_SIZE - 1];
}
return self.unit == other_unit;
}
pub fn unit(&self) -> [f64; units::BASE_UNITS_SIZE] {
return self.unit;
}
pub fn is_unitless(&self) -> bool {
return self.unit.iter().all(|&e| e == 0.0);
}
pub fn try_add(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
if !self.check_compatibility(other.unit) {
return Err("Incompatible units for addition".to_string());
}
return Ok(DimensionalVariable { value: self.value + other.value, unit: self.unit });
}
pub fn try_sub(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
if !self.check_compatibility(other.unit) {
return Err("Incompatible units for subtraction".to_string());
}
return Ok(DimensionalVariable { value: self.value - other.value, unit: self.unit });
}
pub fn powi(&self, exp: i32) -> DimensionalVariable {
let mut unit = self.unit;
for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp as f64; }
return DimensionalVariable { value: self.value.powi(exp), unit };
}
pub fn powf(&self, exp: f64) -> Result<DimensionalVariable, String> {
let mut unit = self.unit;
for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp; }
return Ok(DimensionalVariable { value: self.value.powf(exp), unit });
}
pub fn sqrt(&self) -> Result<DimensionalVariable, String> {
if self.value < 0.0 {
return Err("sqrt of negative value".to_string());
}
return self.powf(0.5);
}
pub fn ln(&self) -> Result<f64, String> {
if !self.is_unitless() { return Err("ln requires a unitless quantity".to_string()); }
if self.value <= 0.0 { return Err("ln domain error (value <= 0)".to_string()); }
Ok(self.value.ln())
}
pub fn log2(&self) -> Result<f64, String> {
if !self.is_unitless() { return Err("log2 requires a unitless quantity".to_string()); }
if self.value <= 0.0 { return Err("log2 domain error (value <= 0)".to_string()); }
Ok(self.value.log2())
}
pub fn log10(&self) -> Result<f64, String> {
if !self.is_unitless() { return Err("log10 requires a unitless quantity".to_string()); }
if self.value <= 0.0 { return Err("log10 domain error (value <= 0)".to_string()); }
Ok(self.value.log10())
}
fn is_angle(&self) -> bool {
for i in 0..units::BASE_UNITS_SIZE - 1 {
if self.unit[i] != 0.0 {
return false;
}
}
self.unit[units::BASE_UNITS_SIZE - 1] == 1.0
}
pub fn sin(&self) -> Result<f64, String> {
if !self.is_angle() {
return Err("sin requires an angle quantity".to_string());
}
Ok(self.value.sin())
}
pub fn cos(&self) -> Result<f64, String> {
if !self.is_angle() {
return Err("cos requires an angle quantity".to_string());
}
Ok(self.value.cos())
}
pub fn tan(&self) -> Result<f64, String> {
if !self.is_angle() {
return Err("tan requires an angle quantity".to_string());
}
Ok(self.value.tan())
}
pub fn asin(&self) -> Result<DimensionalVariable, String> {
if !self.is_unitless() {
return Err("asin requires a unitless quantity".to_string());
}
if self.value < -1.0 || self.value > 1.0 {
return Err("asin requires a value in the range [-1, 1]".to_string());
}
let mut unit = [0.0; units::BASE_UNITS_SIZE];
unit[units::BASE_UNITS_SIZE - 1] = 1.0; Ok(DimensionalVariable { value: self.value.asin(), unit })
}
pub fn acos(&self) -> Result<DimensionalVariable, String> {
if !self.is_unitless() {
return Err("acos requires a unitless quantity".to_string());
}
if self.value < -1.0 || self.value > 1.0 {
return Err("acos requires a value in the range [-1, 1]".to_string());
}
let mut unit = [0.0; units::BASE_UNITS_SIZE];
unit[units::BASE_UNITS_SIZE - 1] = 1.0; Ok(DimensionalVariable { value: self.value.acos(), unit })
}
pub fn atan(&self) -> Result<DimensionalVariable, String> {
if !self.is_unitless() {
return Err("atan requires a unitless quantity".to_string());
}
let mut unit = [0.0; units::BASE_UNITS_SIZE];
unit[units::BASE_UNITS_SIZE - 1] = 1.0; Ok(DimensionalVariable { value: self.value.atan(), unit })
}
pub fn neg(&self) -> DimensionalVariable {
DimensionalVariable { value: -self.value, unit: self.unit }
}
pub fn abs(&self) -> DimensionalVariable {
DimensionalVariable { value: self.value.abs(), unit: self.unit }
}
}
impl std::fmt::Display for DimensionalVariable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut numerator_parts: Vec<String> = Vec::new();
let mut denominator_parts: Vec<String> = Vec::new();
for (i, &exp) in self.unit.iter().enumerate() {
if exp == 0.0 {
continue;
}
let unit_symbol = units::BASE_UNITS[i];
if exp > 0.0 {
if exp == 1.0 {
numerator_parts.push(unit_symbol.to_string());
} else if exp == exp.trunc() {
numerator_parts.push(format!("{}^{}", unit_symbol, exp as i32));
} else {
numerator_parts.push(format!("{}^{}", unit_symbol, exp));
}
} else {
let abs_exp = exp.abs();
if abs_exp == 1.0 {
denominator_parts.push(unit_symbol.to_string());
} else if abs_exp == abs_exp.trunc() {
denominator_parts.push(format!("{}^{}", unit_symbol, abs_exp as i32));
} else {
denominator_parts.push(format!("{}^{}", unit_symbol, abs_exp));
}
}
}
let unit_str = if numerator_parts.is_empty() && denominator_parts.is_empty() {
"(unitless)".to_string()
} else if denominator_parts.is_empty() {
numerator_parts.join("*")
} else if numerator_parts.is_empty() {
format!("1/{}", denominator_parts.join("*"))
} else {
format!("{}/{}", numerator_parts.join("*"), denominator_parts.join("*"))
};
write!(f, "{} {}", self.value, unit_str)
}
}
pub fn asin(x: f64) -> Result<DimensionalVariable, String> {
return DimensionalVariable::new(x, "").unwrap().asin();
}
pub fn acos(x: f64) -> Result<DimensionalVariable, String> {
return DimensionalVariable::new(x, "").unwrap().acos();
}
pub fn atan(x: f64) -> Result<DimensionalVariable, String> {
return DimensionalVariable::new(x, "").unwrap().atan();
}
fn unit_str_to_base_unit(units_str: &str) -> Result<([f64; units::BASE_UNITS_SIZE], f64), String> {
let cleaned_units_str = units_str.replace(['(', ')', '[', ']'], "");
let parts: Vec<&str> = cleaned_units_str.split('/').collect();
if parts.len() > 2 {
return Err("Unit string can only have one '/'".to_string());
}
let mut base_unit = [0.0; units::BASE_UNITS_SIZE];
let mut conversion_factor: f64 = 1.0;
for i in 0..parts.len() {
let denominator_multiplier = if i == 1 { -1 } else { 1 };
let units: Vec<&str> = {
let s = parts[i];
let mut out = Vec::new();
let mut start = 0usize;
let mut prev: Option<char> = None;
for (idx, ch) in s.char_indices() {
if ch == '-' && prev != Some('^') {
if idx > start {
out.push(&s[start..idx]);
}
start = idx + ch.len_utf8();
}
prev = Some(ch);
}
if start < s.len() {
out.push(&s[start..]);
}
out
};
for unit_str in units {
let (base, power) = read_unit_power(unit_str)?;
let unit_map = units::unit_map();
let unit = unit_map.get(base)
.ok_or_else(|| format!("Unknown unit: {}", base))?;
for j in 0..units::BASE_UNITS_SIZE {
base_unit[j] += unit.base_unit[j] * (power * denominator_multiplier) as f64;
}
conversion_factor *= unit.conversion_factor.powi(power * denominator_multiplier);
}
}
return Ok((base_unit, conversion_factor));
}
fn read_unit_power(unit: &str) -> Result<(&str, i32), String> {
let u = unit.trim();
if u.is_empty() {
return Err("Empty unit token".to_string());
}
let bytes = u.as_bytes();
let mut end = u.len();
while end > 0 && bytes[end - 1].is_ascii_digit() {
end -= 1;
}
if end == u.len() {
if end > 0 && bytes[end - 1] == b'^' {
return Err(format!("Missing exponent after '^' in \"{}\"", u));
}
return Ok((u, 1));
}
let mut start = end;
if start > 0 && (bytes[start - 1] == b'-') {
start -= 1;
}
let exp_str = &u[start..];
let exp: i32 = exp_str
.parse()
.map_err(|_| format!("Unable to read numeric power from \"{}\"", u))?;
let mut base_end = start;
if base_end > 0 && bytes[base_end - 1] == b'^' {
base_end -= 1;
}
let base = u[..base_end].trim();
if base.is_empty() {
return Err(format!("Missing unit symbol before exponent in \"{}\"", u));
}
Ok((base, exp))
}
fn add_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
let mut out = a;
for i in 0..units::BASE_UNITS_SIZE { out[i] += b[i]; }
out
}
fn sub_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
let mut out = a;
for i in 0..units::BASE_UNITS_SIZE { out[i] -= b[i]; }
out
}
use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign};
use std::cmp::Ordering;
impl<'a, 'b> Add<&'b DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
return self.try_add(rhs).expect("Incompatible units for addition");
}
}
impl Add<DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn add(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, &rhs)
}
}
impl<'b> Add<&'b DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, rhs)
}
}
impl<'a> Add<DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn add(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Add<&DimensionalVariable>>::add(self, &rhs)
}
}
impl<'a, 'b> Sub<&'b DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
return self.try_sub(rhs).expect("Incompatible units for subtraction");
}
}
impl Sub<DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn sub(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, &rhs)
}
}
impl<'b> Sub<&'b DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, rhs)
}
}
impl<'a> Sub<DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn sub(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Sub<&DimensionalVariable>>::sub(self, &rhs)
}
}
impl<'a, 'b> Mul<&'b DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
DimensionalVariable { value: self.value * rhs.value, unit: add_unit_exponents(self.unit, rhs.unit) }
}
}
impl Mul<DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn mul(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, &rhs)
}
}
impl<'b> Mul<&'b DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, rhs)
}
}
impl<'a> Mul<DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn mul(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Mul<&DimensionalVariable>>::mul(self, &rhs)
}
}
impl<'a, 'b> Div<&'b DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
DimensionalVariable { value: self.value / rhs.value, unit: sub_unit_exponents(self.unit, rhs.unit) }
}
}
impl Div<DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn div(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, &rhs)
}
}
impl<'b> Div<&'b DimensionalVariable> for DimensionalVariable {
type Output = DimensionalVariable;
fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, rhs)
}
}
impl<'a> Div<DimensionalVariable> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn div(self, rhs: DimensionalVariable) -> Self::Output {
<&DimensionalVariable as Div<&DimensionalVariable>>::div(self, &rhs)
}
}
impl AddAssign<&DimensionalVariable> for DimensionalVariable {
fn add_assign(&mut self, rhs: &DimensionalVariable) {
*self = self.try_add(rhs).expect("Incompatible units for addition assignment");
}
}
impl SubAssign<&DimensionalVariable> for DimensionalVariable {
fn sub_assign(&mut self, rhs: &DimensionalVariable) {
*self = self.try_sub(rhs).expect("Incompatible units for subtraction assignment");
}
}
impl MulAssign<&DimensionalVariable> for DimensionalVariable {
fn mul_assign(&mut self, rhs: &DimensionalVariable) {
self.value *= rhs.value;
self.unit = add_unit_exponents(self.unit, rhs.unit);
}
}
impl DivAssign<&DimensionalVariable> for DimensionalVariable {
fn div_assign(&mut self, rhs: &DimensionalVariable) {
self.value /= rhs.value;
self.unit = sub_unit_exponents(self.unit, rhs.unit);
}
}
impl<'a> Mul<f64> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn mul(self, rhs: f64) -> Self::Output {
DimensionalVariable { value: self.value * rhs, unit: self.unit }
}
}
impl Mul<f64> for DimensionalVariable {
type Output = DimensionalVariable;
fn mul(self, rhs: f64) -> Self::Output {
<&DimensionalVariable as Mul<f64>>::mul(&self, rhs)
}
}
impl MulAssign<f64> for DimensionalVariable {
fn mul_assign(&mut self, rhs: f64) {
self.value *= rhs;
}
}
impl<'a> Div<f64> for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn div(self, rhs: f64) -> Self::Output {
DimensionalVariable { value: self.value / rhs, unit: self.unit }
}
}
impl Div<f64> for DimensionalVariable {
type Output = DimensionalVariable;
fn div(self, rhs: f64) -> Self::Output {
<&DimensionalVariable as Div<f64>>::div(&self, rhs)
}
}
impl DivAssign<f64> for DimensionalVariable {
fn div_assign(&mut self, rhs: f64) {
self.value /= rhs;
}
}
impl<'a> Mul<&'a DimensionalVariable> for f64 {
type Output = DimensionalVariable;
fn mul(self, rhs: &'a DimensionalVariable) -> Self::Output {
DimensionalVariable { value: self * rhs.value, unit: rhs.unit }
}
}
impl Mul<DimensionalVariable> for f64 {
type Output = DimensionalVariable;
fn mul(self, rhs: DimensionalVariable) -> Self::Output {
<f64 as Mul<&DimensionalVariable>>::mul(self, &rhs)
}
}
impl<'a> Div<&'a DimensionalVariable> for f64 {
type Output = DimensionalVariable;
fn div(self, rhs: &'a DimensionalVariable) -> Self::Output {
DimensionalVariable { value: self / rhs.value, unit: sub_unit_exponents([0.0; units::BASE_UNITS_SIZE], rhs.unit) }
}
}
impl Div<DimensionalVariable> for f64 {
type Output = DimensionalVariable;
fn div(self, rhs: DimensionalVariable) -> Self::Output {
<f64 as Div<&DimensionalVariable>>::div(self, &rhs)
}
}
impl<'a> Neg for &'a DimensionalVariable {
type Output = DimensionalVariable;
fn neg(self) -> Self::Output {
DimensionalVariable { value: -self.value, unit: self.unit }
}
}
impl Neg for DimensionalVariable {
type Output = DimensionalVariable;
fn neg(self) -> Self::Output {
<&DimensionalVariable as Neg>::neg(&self)
}
}
impl PartialEq for DimensionalVariable {
fn eq(&self, other: &Self) -> bool {
if !self.check_compatibility(other.unit) {
return false;
}
self.value == other.value
}
}
impl PartialOrd for DimensionalVariable {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if !self.check_compatibility(other.unit) { return None; }
self.value.partial_cmp(&other.value)
}
}
#[cfg(test)]
mod tests {
use super::read_unit_power;
#[test]
fn read_unit_power_basic_cases() {
assert_eq!(read_unit_power("m").unwrap(), ("m", 1));
assert_eq!(read_unit_power("m3").unwrap(), ("m", 3));
assert_eq!(read_unit_power("m^3").unwrap(), ("m", 3));
assert_eq!(read_unit_power("m^-2").unwrap(), ("m", -2));
assert_eq!(read_unit_power("m-2").unwrap(), ("m", -2));
assert_eq!(read_unit_power(" kg^2 ").unwrap(), ("kg", 2));
assert_eq!(read_unit_power("undef").unwrap(), ("undef", 1)); }
#[test]
fn read_unit_power_errors() {
assert!(read_unit_power("").is_err());
let err = read_unit_power("m^").unwrap_err();
assert!(err.contains("Missing exponent"));
let err = read_unit_power("^2").unwrap_err();
assert!(err.contains("Missing unit symbol"));
}
}