#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#[cfg(not(feature = "std"))]
extern crate libm;
mod dimension;
#[cfg(feature = "diesel")]
mod feature_diesel;
#[cfg(feature = "pyo3")]
mod feature_pyo3;
#[cfg(feature = "serde")]
mod feature_serde;
#[cfg(feature = "tiberius")]
mod feature_tiberius;
mod macros;
mod quantity;
pub mod scalar;
mod unit;
pub use dimension::{
Acceleration,
AmountOfSubstance,
Angular,
Area,
Current,
Dim,
DimDiv,
DimMul,
Dimension,
Dimensionless,
Energy,
Force,
FrequencyDim,
Length,
LuminousIntensity,
Mass,
Power,
Temperature,
Time,
VelocityDim,
Volume,
};
pub use quantity::{
Quantity, Quantity32, Quantity64, QuantityI128, QuantityI16, QuantityI32, QuantityI64,
QuantityI8,
};
pub use scalar::{Exact, IntegerScalar, Real, Scalar, Transcendental};
pub use unit::{Per, Prod, Simplify, Unit, Unitless};
#[cfg(feature = "scalar-decimal")]
pub use quantity::QuantityDecimal;
#[cfg(feature = "scalar-rational")]
pub use quantity::QuantityRational;
#[cfg(feature = "serde")]
pub use feature_serde::serde_with_unit;
#[cfg(feature = "serde")]
pub use feature_serde::serde_scalar;
pub mod units;
pub use units::angular;
pub use units::area;
pub use units::frequency;
pub use units::length;
pub use units::mass;
pub use units::power;
pub use units::time;
pub use units::unitless;
pub use units::velocity;
pub use units::volume;
#[cfg(test)]
mod tests {
use super::*;
type TestDim = Length;
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum TestUnit {}
impl Unit for TestUnit {
const RATIO: f64 = 1.0;
type Dim = TestDim;
const SYMBOL: &'static str = "tu";
}
impl core::fmt::Display for Quantity<TestUnit> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} tu", self.value())
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum DoubleTestUnit {}
impl Unit for DoubleTestUnit {
const RATIO: f64 = 2.0;
type Dim = TestDim;
const SYMBOL: &'static str = "dtu";
}
impl core::fmt::Display for Quantity<DoubleTestUnit> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} dtu", self.value())
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum HalfTestUnit {}
impl Unit for HalfTestUnit {
const RATIO: f64 = 0.5;
type Dim = TestDim;
const SYMBOL: &'static str = "htu";
}
impl core::fmt::Display for Quantity<HalfTestUnit> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{} htu", self.value())
}
}
type TU = Quantity<TestUnit>;
type Dtu = Quantity<DoubleTestUnit>;
#[test]
fn quantity_new_and_value() {
let q = TU::new(42.0);
assert_eq!(q.value(), 42.0);
}
#[test]
fn quantity_nan_constant() {
assert!(TU::NAN.value().is_nan());
}
#[test]
fn quantity_abs() {
assert_eq!(TU::new(-5.0).abs().value(), 5.0);
assert_eq!(TU::new(5.0).abs().value(), 5.0);
assert_eq!(TU::new(0.0).abs().value(), 0.0);
}
#[test]
fn quantity_from_f64() {
let q: TU = 123.456.into();
assert_eq!(q.value(), 123.456);
}
#[test]
fn quantity_has_scalar_layout() {
assert_eq!(
core::mem::size_of::<Quantity<TestUnit>>(),
core::mem::size_of::<f64>()
);
assert_eq!(
core::mem::align_of::<Quantity<TestUnit>>(),
core::mem::align_of::<f64>()
);
}
#[test]
fn quantity_conversion_to_same_unit() {
let q = TU::new(10.0);
let converted = q.to::<TestUnit>();
assert_eq!(converted.value(), 10.0);
}
#[test]
fn quantity_conversion_to_different_unit() {
let q = TU::new(10.0);
let converted = q.to::<DoubleTestUnit>();
assert!((converted.value() - 5.0).abs() < 1e-12);
}
#[test]
fn quantity_conversion_roundtrip() {
let original = TU::new(100.0);
let converted = original.to::<DoubleTestUnit>();
let back = converted.to::<TestUnit>();
assert!((back.value() - original.value()).abs() < 1e-12);
}
#[test]
fn const_add() {
let a = TU::new(3.0);
let b = TU::new(7.0);
assert_eq!(a.const_add(b).value(), 10.0);
}
#[test]
fn const_sub() {
let a = TU::new(10.0);
let b = TU::new(3.0);
assert_eq!(a.const_sub(b).value(), 7.0);
}
#[test]
fn const_mul() {
let a = TU::new(4.0);
let b = 5.0;
assert_eq!(a.const_mul(b).value(), 20.0);
}
#[test]
fn const_div() {
let a = TU::new(20.0);
let b = 4.0;
assert_eq!(a.const_div(b).value(), 5.0);
}
#[test]
fn const_min() {
let a = TU::new(5.0);
let b = TU::new(3.0);
assert_eq!(a.min_const(b).value(), 3.0);
assert_eq!(b.min_const(a).value(), 3.0);
}
#[test]
fn const_max() {
let a = TU::new(3.0);
let b = TU::new(5.0);
assert_eq!(a.max_const(b).value(), 5.0);
assert_eq!(b.max_const(a).value(), 5.0);
}
#[test]
fn sum_quantities_owned() {
let qs = vec![TU::new(1.0), TU::new(2.0), TU::new(3.0)];
let total: TU = qs.into_iter().sum();
assert_eq!(total.value(), 6.0);
}
#[test]
fn sum_quantities_by_ref() {
let qs = [TU::new(1.0), TU::new(2.0), TU::new(3.0)];
let total: TU = qs.iter().sum();
assert_eq!(total.value(), 6.0);
}
#[test]
fn sum_quantities_into_f64() {
let qs = vec![TU::new(1.0), TU::new(2.0), TU::new(3.0)];
let total: f64 = qs.into_iter().sum();
assert_eq!(total, 6.0);
}
#[test]
fn operator_add() {
let a = TU::new(3.0);
let b = TU::new(7.0);
assert_eq!((a + b).value(), 10.0);
}
#[test]
fn operator_sub() {
let a = TU::new(10.0);
let b = TU::new(3.0);
assert_eq!((a - b).value(), 7.0);
}
#[test]
fn operator_mul_by_f64() {
let q = TU::new(5.0);
assert_eq!((q * 3.0).value(), 15.0);
assert_eq!((3.0 * q).value(), 15.0);
}
#[test]
fn operator_div_by_f64() {
let q = TU::new(15.0);
assert_eq!((q / 3.0).value(), 5.0);
}
#[test]
fn operator_neg() {
let q = TU::new(5.0);
assert_eq!((-q).value(), -5.0);
assert_eq!((-(-q)).value(), 5.0);
}
#[test]
fn operator_rem() {
let q = TU::new(10.0);
assert_eq!((q % 3.0).value(), 1.0);
}
#[test]
fn operator_add_assign() {
let mut q = TU::new(5.0);
q += TU::new(3.0);
assert_eq!(q.value(), 8.0);
}
#[test]
fn operator_sub_assign() {
let mut q = TU::new(10.0);
q -= TU::new(3.0);
assert_eq!(q.value(), 7.0);
}
#[test]
fn operator_div_assign() {
let mut q = TU::new(20.0);
q /= 4.0;
assert_eq!(q.value(), 5.0);
}
#[test]
fn partial_eq_f64() {
let q = TU::new(5.0);
assert!(q == 5.0);
assert!(!(q == 4.0));
}
#[test]
fn division_creates_per_type() {
let num = TU::new(100.0);
let den = Dtu::new(20.0);
let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = num / den;
assert!((ratio.value() - 5.0).abs() < 1e-12);
}
#[test]
fn per_ratio_conversion() {
let v1: Quantity<Per<DoubleTestUnit, TestUnit>> = Quantity::new(10.0);
let v2: Quantity<Per<TestUnit, TestUnit>> = v1.to();
assert!((v2.value() - 20.0).abs() < 1e-12);
}
#[test]
fn per_multiplication_recovers_numerator() {
let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
let time = Dtu::new(4.0);
let result: TU = (rate * time).to();
assert!((result.value() - 20.0).abs() < 1e-12);
}
#[test]
fn per_multiplication_commutative() {
let rate: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(5.0);
let time = Dtu::new(4.0);
let result1: TU = (rate * time).to();
let result2: TU = (time * rate).to();
assert!((result1.value() - result2.value()).abs() < 1e-12);
}
#[test]
fn simplify_per_u_u_to_unitless() {
let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.23456);
let unitless: Quantity<Unitless> = ratio.simplify();
assert!((unitless.value() - 1.23456).abs() < 1e-12);
}
#[test]
fn simplify_per_n_per_n_d_to_d() {
let q: Quantity<Per<TestUnit, Per<TestUnit, DoubleTestUnit>>> = Quantity::new(7.5);
let simplified: Dtu = q.simplify();
assert!((simplified.value() - 7.5).abs() < 1e-12);
}
#[test]
fn per_u_u_asin() {
let ratio: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.5);
let result = ratio.asin();
assert!((result - 0.5_f64.asin()).abs() < 1e-12);
}
#[test]
fn per_u_u_asin_boundary_values() {
let one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(1.0);
assert!((one.asin() - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
let neg_one: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(-1.0);
assert!((neg_one.asin() - (-core::f64::consts::FRAC_PI_2)).abs() < 1e-12);
let zero: Quantity<Per<TestUnit, TestUnit>> = Quantity::new(0.0);
assert!((zero.asin() - 0.0).abs() < 1e-12);
}
#[test]
fn display_simple_quantity() {
let q = TU::new(42.5);
let s = format!("{}", q);
assert_eq!(s, "42.5 tu");
}
#[test]
fn display_per_quantity() {
let q: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(2.5);
let s = format!("{}", q);
assert_eq!(s, "2.5 tu/dtu");
}
#[test]
fn display_negative_value() {
let q = TU::new(-99.9);
let s = format!("{}", q);
assert_eq!(s, "-99.9 tu");
}
#[test]
fn display_double_test_unit() {
let q = Dtu::new(2.5);
let s = format!("{}", q);
assert_eq!(s, "2.5 dtu");
}
#[test]
fn display_half_test_unit() {
type Htu = Quantity<HalfTestUnit>;
let q = Htu::new(3.0);
let s = format!("{}", q);
assert_eq!(s, "3 htu");
}
#[test]
fn edge_case_zero() {
let zero = TU::new(0.0);
assert_eq!(zero.value(), 0.0);
assert_eq!((-zero).value(), 0.0);
assert_eq!(zero.abs().value(), 0.0);
}
#[test]
fn edge_case_negative_values() {
let neg = TU::new(-10.0);
let pos = TU::new(5.0);
assert_eq!((neg + pos).value(), -5.0);
assert_eq!((neg - pos).value(), -15.0);
assert_eq!((neg * 2.0).value(), -20.0);
assert_eq!(neg.abs().value(), 10.0);
}
#[test]
fn edge_case_large_values() {
let large = TU::new(1e100);
let small = TU::new(1e-100);
assert_eq!(large.value(), 1e100);
assert_eq!(small.value(), 1e-100);
}
#[test]
fn edge_case_infinity() {
let inf = TU::new(f64::INFINITY);
let neg_inf = TU::new(f64::NEG_INFINITY);
assert!(inf.value().is_infinite());
assert!(neg_inf.value().is_infinite());
assert_eq!(inf.value().signum(), 1.0);
assert_eq!(neg_inf.value().signum(), -1.0);
}
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
use serde::{Deserialize, Serialize};
#[test]
fn serialize_quantity() {
let q = TU::new(42.5);
let json = serde_json::to_string(&q).unwrap();
assert_eq!(json, "42.5");
}
#[test]
fn deserialize_quantity() {
let json = "42.5";
let q: TU = serde_json::from_str(json).unwrap();
assert_eq!(q.value(), 42.5);
}
#[test]
fn serde_roundtrip() {
let original = TU::new(123.456);
let json = serde_json::to_string(&original).unwrap();
let restored: TU = serde_json::from_str(&json).unwrap();
assert!((restored.value() - original.value()).abs() < 1e-12);
}
#[derive(Serialize, Deserialize, Debug)]
struct TestStruct {
#[serde(with = "crate::serde_with_unit")]
distance: TU,
}
#[test]
fn serde_with_unit_serialize() {
let data = TestStruct {
distance: TU::new(42.5),
};
let json = serde_json::to_string(&data).unwrap();
assert!(json.contains("\"value\""));
assert!(json.contains("\"unit\""));
assert!(json.contains("42.5"));
assert!(json.contains("\"tu\""));
}
#[test]
fn serde_with_unit_deserialize() {
let json = r#"{"distance":{"value":42.5,"unit":"tu"}}"#;
let data: TestStruct = serde_json::from_str(json).unwrap();
assert_eq!(data.distance.value(), 42.5);
}
#[test]
fn serde_with_unit_deserialize_no_unit_field() {
let json = r#"{"distance":{"value":42.5}}"#;
let data: TestStruct = serde_json::from_str(json).unwrap();
assert_eq!(data.distance.value(), 42.5);
}
#[test]
fn serde_with_unit_deserialize_wrong_unit() {
let json = r#"{"distance":{"value":42.5,"unit":"wrong"}}"#;
let result: Result<TestStruct, _> = serde_json::from_str(json);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("unit mismatch") || err_msg.contains("expected"));
}
#[test]
fn serde_with_unit_deserialize_missing_value() {
let json = r#"{"distance":{"unit":"tu"}}"#;
let result: Result<TestStruct, _> = serde_json::from_str(json);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("missing field") || err_msg.contains("value"));
}
#[test]
fn serde_with_unit_deserialize_duplicate_value() {
let json = r#"{"distance":{"value":42.5,"value":100.0,"unit":"tu"}}"#;
let result: Result<TestStruct, _> = serde_json::from_str(json);
let _ = result;
}
#[test]
fn serde_with_unit_deserialize_duplicate_unit() {
let json = r#"{"distance":{"value":42.5,"unit":"tu","unit":"tu"}}"#;
let result: Result<TestStruct, _> = serde_json::from_str(json);
let _ = result;
}
#[test]
fn serde_with_unit_deserialize_invalid_format() {
let json = r#"{"distance":"not_an_object"}"#;
let result: Result<TestStruct, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[test]
fn serde_with_unit_deserialize_array() {
let json = r#"{"distance":[42.5, "tu"]}"#;
let result: Result<TestStruct, _> = serde_json::from_str(json);
assert!(result.is_err());
}
#[test]
fn serde_with_unit_roundtrip() {
let original = TestStruct {
distance: TU::new(123.456),
};
let json = serde_json::to_string(&original).unwrap();
let restored: TestStruct = serde_json::from_str(&json).unwrap();
assert!((restored.distance.value() - original.distance.value()).abs() < 1e-12);
}
#[test]
fn serde_with_unit_special_values() {
let test_large = TestStruct {
distance: TU::new(1e100),
};
let json = serde_json::to_string(&test_large).unwrap();
let restored: TestStruct = serde_json::from_str(&json).unwrap();
assert!((restored.distance.value() - 1e100).abs() < 1e88);
let test_small = TestStruct {
distance: TU::new(-1e-100),
};
let json = serde_json::to_string(&test_small).unwrap();
let restored: TestStruct = serde_json::from_str(&json).unwrap();
assert!((restored.distance.value() + 1e-100).abs() < 1e-112);
}
}
}