use qtty_core::*;
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";
}
#[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";
}
#[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";
}
qtty_core::impl_unit_arithmetic_pairs!(TestUnit, DoubleTestUnit, HalfTestUnit);
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_ceil() {
assert_eq!(TU::new(3.2).ceil().value(), 4.0);
assert_eq!(TU::new(-3.2).ceil().value(), -3.0);
assert_eq!(TU::new(5.0).ceil().value(), 5.0);
}
#[test]
fn quantity_floor() {
assert_eq!(TU::new(3.7).floor().value(), 3.0);
assert_eq!(TU::new(-3.7).floor().value(), -4.0);
assert_eq!(TU::new(5.0).floor().value(), 5.0);
}
#[test]
fn quantity_round() {
assert_eq!(TU::new(3.5).round().value(), 4.0);
assert_eq!(TU::new(3.4).round().value(), 3.0);
assert_eq!(TU::new(-3.5).round().value(), -4.0);
}
#[test]
fn quantity_trunc() {
assert_eq!(TU::new(3.9).trunc().value(), 3.0);
assert_eq!(TU::new(-3.9).trunc().value(), -3.0);
}
#[test]
fn quantity_fract() {
assert!((TU::new(3.75).fract().value() - 0.75).abs() < 1e-12);
assert!((TU::new(-3.75).fract().value() + 0.75).abs() < 1e-12);
}
#[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::<TU>(), core::mem::size_of::<f64>());
assert_eq!(core::mem::align_of::<TU>(), 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 exact_checked_lossy_same_ratio_preserves_large_integer() {
let q = Quantity::<TestUnit, i64>::new(i64::MAX);
let converted = q.checked_to_lossy::<TestUnit>().unwrap();
assert_eq!(converted.value(), i64::MAX);
}
#[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(b).value(), 3.0);
assert_eq!(b.min(a).value(), 3.0);
}
#[test]
fn quantity_clamp_with_valid_bounds() {
let q = TU::new(7.5);
assert_eq!(q.clamp(TU::new(0.0), TU::new(10.0)).value(), 7.5);
assert_eq!(q.clamp(TU::new(8.0), TU::new(10.0)).value(), 8.0);
assert_eq!(q.clamp(TU::new(0.0), TU::new(7.0)).value(), 7.0);
}
#[cfg(debug_assertions)]
#[test]
#[should_panic(expected = "Quantity::clamp requires min_val <= max_val")]
fn quantity_clamp_panics_on_inverted_bounds_in_debug() {
let _ = TU::new(5.0).clamp(TU::new(10.0), TU::new(0.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 iterator_sum_quantity_owned() {
let values = vec![TU::new(1.0), TU::new(2.5), TU::new(3.5)];
let total: TU = values.into_iter().sum();
assert!((total.value() - 7.0).abs() < 1e-12);
}
#[test]
fn iterator_sum_quantity_borrowed() {
let values = [TU::new(1.0), TU::new(2.5), TU::new(3.5)];
let total: TU = values.iter().sum();
assert!((total.value() - 7.0).abs() < 1e-12);
}
#[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_same_unit() {
let q = TU::new(5.0);
assert!(q == TU::new(5.0));
assert!(!(q == TU::new(4.0)));
}
#[test]
fn division_same_unit_gives_raw_scalar() {
let a = TU::new(100.0);
let b = TU::new(20.0);
let ratio: f64 = a / b;
assert!((ratio - 5.0).abs() < 1e-12);
}
#[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;
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;
let result2: TU = time * rate;
assert!((result1.value() - result2.value()).abs() < 1e-12);
}
#[test]
fn dimensionless_asin_angle() {
use qtty_core::units::angular::Radian;
let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(0.5);
let result: Quantity<Radian> = ratio.asin_angle();
assert!((result.value() - 0.5_f64.asin()).abs() < 1e-12);
}
#[test]
fn same_unit_ratio_is_raw_scalar() {
let ratio: f64 = TU::new(1.0) / TU::new(2.0);
assert!((ratio - 0.5).abs() < 1e-12);
}
#[test]
fn dimensionless_asin_angle_boundary_values() {
let one: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(1.0);
assert!((one.asin_angle().value() - core::f64::consts::FRAC_PI_2).abs() < 1e-12);
let neg_one: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(-1.0);
assert!((neg_one.asin_angle().value() - (-core::f64::consts::FRAC_PI_2)).abs() < 1e-12);
let zero: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(0.0);
assert!((zero.asin_angle().value() - 0.0).abs() < 1e-12);
}
#[test]
fn asin_angle_to_degrees() {
use qtty_core::units::angular::{Degree, Radian};
let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(0.5);
let angle: Quantity<Radian> = ratio.asin_angle();
let deg: Quantity<Degree> = angle.to();
assert!((deg.value() - 30.0).abs() < 1e-10);
}
#[test]
fn asin_angle_sin_roundtrip() {
let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(0.75);
let angle = ratio.asin_angle();
assert!((angle.sin() - 0.75).abs() < 1e-12);
}
#[test]
fn display_simple_quantity() {
let q = TU::new(42.5);
let s = format!("{} {}", q.value(), TestUnit::SYMBOL);
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.value(), TestUnit::SYMBOL);
assert_eq!(s, "-99.9 tu");
}
#[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);
}
#[test]
fn mean_positive_infinity_stays_infinite() {
let inf = TU::INFINITY;
assert!(inf.mean(inf).value().is_infinite());
assert!(inf.mean(inf).value() > 0.0);
}
#[test]
fn mean_negative_infinity_stays_infinite() {
let neg_inf = TU::NEG_INFINITY;
assert!(neg_inf.mean(neg_inf).value().is_infinite());
assert!(neg_inf.mean(neg_inf).value() < 0.0);
}
#[test]
fn mean_finite_values_unaffected() {
assert_eq!(TU::new(10.0).mean(TU::new(14.0)).value(), 12.0);
assert_eq!(
TU::new(i64::MAX as f64)
.mean(TU::new(i64::MAX as f64))
.value(),
i64::MAX as f64
);
}
#[test]
fn mean_pos_infinity_with_finite_stays_infinite() {
let inf = TU::INFINITY;
let fin = TU::new(1.0);
assert!(inf.mean(fin).value().is_infinite());
assert!(inf.mean(fin).value() > 0.0);
assert!(fin.mean(inf).value().is_infinite());
assert!(fin.mean(inf).value() > 0.0);
}
#[test]
fn mean_neg_infinity_with_finite_stays_infinite() {
let neg_inf = TU::NEG_INFINITY;
let fin = TU::new(-1.0);
assert!(neg_inf.mean(fin).value().is_infinite());
assert!(neg_inf.mean(fin).value() < 0.0);
assert!(fin.mean(neg_inf).value().is_infinite());
assert!(fin.mean(neg_inf).value() < 0.0);
}
#[test]
fn test_is_nan() {
let nan = TU::new(f64::NAN);
let normal = TU::new(42.0);
assert!(nan.is_nan());
assert!(!normal.is_nan());
}
#[test]
fn test_is_infinite() {
let inf = TU::new(f64::INFINITY);
let neg_inf = TU::new(f64::NEG_INFINITY);
let normal = TU::new(42.0);
assert!(inf.is_infinite());
assert!(neg_inf.is_infinite());
assert!(!normal.is_infinite());
}
#[test]
fn test_is_finite() {
let normal = TU::new(42.0);
let inf = TU::new(f64::INFINITY);
let nan = TU::new(f64::NAN);
assert!(normal.is_finite());
assert!(!inf.is_finite());
assert!(!nan.is_finite());
}
#[test]
fn test_signum() {
let pos = TU::new(42.0);
let neg = TU::new(-42.0);
assert_eq!(pos.signum(), 1.0);
assert_eq!(neg.signum(), -1.0);
}
#[test]
fn test_scalar_sqrt() {
let q = TU::new(16.0);
let sqrt_val = q.scalar_sqrt();
assert!((sqrt_val - 4.0).abs() < 1e-12);
}
#[test]
fn test_cast() {
let q = TU::new(42.5);
let q_f32: Quantity<TestUnit, f32> = q.cast();
assert!((q_f32.value() - 42.5).abs() < 0.01);
}
#[test]
fn test_max() {
let a = TU::new(5.0);
let b = TU::new(10.0);
assert_eq!(a.max(b).value(), 10.0);
assert_eq!(b.max(a).value(), 10.0);
}
#[test]
fn test_mean() {
let a = TU::new(10.0);
let b = TU::new(14.0);
assert_eq!(a.mean(b).value(), 12.0);
assert_eq!(b.mean(a).value(), 12.0);
}
#[test]
fn test_acos_angle() {
use qtty_core::units::angular::Radian;
let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(0.5);
let result: Quantity<Radian> = ratio.acos_angle();
assert!((result.value() - 0.5_f64.acos()).abs() < 1e-12);
}
#[test]
fn test_atan_angle() {
use qtty_core::units::angular::Radian;
let ratio: Quantity<Per<TestUnit, DoubleTestUnit>> = Quantity::new(1.0);
let result: Quantity<Radian> = ratio.atan_angle();
assert!((result.value() - core::f64::consts::FRAC_PI_4).abs() < 1e-12);
}
#[test]
fn test_to_lossy() {
use qtty_core::units::length::{Kilometer, Meter};
let km: Quantity<Kilometer, i32> = Quantity::new(5);
let m: Quantity<Meter, i32> = km.to_lossy();
assert_eq!(m.value(), 5000);
}
#[test]
fn test_value_ref() {
let q = TU::new(42.5);
assert_eq!(*q.value_ref(), 42.5);
}
#[test]
fn test_zero_one() {
let zero = TU::zero();
let one = TU::one();
assert_eq!(zero.value(), 0.0);
assert_eq!(one.value(), 1.0);
}
#[test]
fn test_min_const() {
let a = TU::new(3.0);
let b = TU::new(7.0);
assert_eq!(a.min_const(b).value(), 3.0);
}
#[test]
fn test_min_const_else_branch() {
let a = TU::new(7.0);
let b = TU::new(3.0);
assert_eq!(a.min_const(b).value(), 3.0);
}
#[test]
fn test_max_const() {
let a = TU::new(3.0);
let b = TU::new(7.0);
assert_eq!(a.max_const(b).value(), 7.0);
}
#[test]
fn test_max_const_then_branch() {
let a = TU::new(7.0);
let b = TU::new(3.0);
assert_eq!(a.max_const(b).value(), 7.0);
}
#[test]
fn test_to_const() {
let q = TU::new(10.0);
let converted: Dtu = q.to_const();
assert!((converted.value() - 5.0).abs() < 1e-12);
}
#[test]
fn partial_ord_same_unit() {
let a = TU::new(3.0);
let b = TU::new(7.0);
assert!(a < b);
assert!(a <= b);
assert!(b > a);
assert!(b >= a);
assert!(a <= a);
assert!(a >= a);
}
#[test]
fn partial_ord_same_unit_more() {
let q = TU::new(5.0);
assert!(q > TU::new(3.0));
assert!(q >= TU::new(5.0));
assert!(q < TU::new(10.0));
assert!(q <= TU::new(5.0));
}
#[test]
fn partial_ord_nan_returns_none() {
let nan = TU::new(f64::NAN);
let normal = TU::new(5.0);
assert!(nan.partial_cmp(&normal).is_none());
assert!(!(nan == normal));
}
#[test]
fn eq_ord_integers() {
use core::cmp::Ordering;
let a: Quantity<TestUnit, i32> = Quantity::new(3);
let b: Quantity<TestUnit, i32> = Quantity::new(7);
let c: Quantity<TestUnit, i32> = Quantity::new(3);
assert_eq!(a, c);
assert_ne!(a, b);
assert_eq!(a.cmp(&b), Ordering::Less);
assert_eq!(b.cmp(&a), Ordering::Greater);
assert_eq!(a.cmp(&c), Ordering::Equal);
}
#[test]
fn ord_integers_sort() {
let mut quantities: Vec<Quantity<TestUnit, i32>> = vec![
Quantity::new(5),
Quantity::new(1),
Quantity::new(3),
Quantity::new(2),
Quantity::new(4),
];
quantities.sort();
let values: Vec<i32> = quantities.iter().map(|q| q.value()).collect();
assert_eq!(values, vec![1, 2, 3, 4, 5]);
}
#[test]
fn ord_integers_min_max() {
let a: Quantity<TestUnit, i32> = Quantity::new(3);
let b: Quantity<TestUnit, i32> = Quantity::new(7);
assert_eq!(a.min(b).value(), 3);
assert_eq!(a.max(b).value(), 7);
assert_eq!(a.mean(b).value(), 5);
}
#[test]
fn ord_integers_btreemap() {
use std::collections::BTreeMap;
let mut map: BTreeMap<Quantity<TestUnit, i32>, &str> = BTreeMap::new();
map.insert(Quantity::new(3), "three");
map.insert(Quantity::new(1), "one");
map.insert(Quantity::new(2), "two");
let keys: Vec<i32> = map.keys().map(|q| q.value()).collect();
assert_eq!(keys, vec![1, 2, 3]);
}
#[test]
fn cross_unit_eq_method() {
let dtu = Dtu::new(5.0);
let tu = TU::new(10.0);
assert!(dtu.eq_unit(&tu));
assert!(tu.eq_unit(&dtu));
}
#[test]
fn cross_unit_cmp_method() {
use core::cmp::Ordering;
let dtu = Dtu::new(5.0); let tu_less = TU::new(3.0);
let tu_more = TU::new(20.0);
let tu_eq = TU::new(10.0);
assert_eq!(dtu.cmp_unit(&tu_less), Some(Ordering::Greater));
assert_eq!(dtu.cmp_unit(&tu_more), Some(Ordering::Less));
assert_eq!(dtu.cmp_unit(&tu_eq), Some(Ordering::Equal));
}
#[test]
#[cfg(feature = "cross-unit-ops")]
fn cross_unit_operators_via_macro() {
use qtty_core::units::length::{Kilometer, Meter};
let km: Quantity<Kilometer> = Quantity::new(1.0);
let m: Quantity<Meter> = Quantity::new(500.0);
assert!(km > m);
assert!(km >= m);
assert!(m < km);
assert!(m <= km);
assert!(km != m);
let m_eq: Quantity<Meter> = Quantity::new(1000.0);
assert!(km == m_eq);
assert!(km >= m_eq);
assert!(km <= m_eq);
assert!(m_eq == km);
assert!(m_eq >= km);
assert!(m_eq <= km);
}
#[test]
#[cfg(feature = "cross-unit-ops")]
fn cross_unit_operators_f32() {
use qtty_core::units::length::{Kilometer, Meter};
let km: Quantity<Kilometer, f32> = Quantity::new(2.0_f32);
let m: Quantity<Meter, f32> = Quantity::new(1500.0_f32);
assert!(km > m);
assert!(m < km);
}
#[test]
#[cfg(feature = "cross-unit-ops")]
fn cross_unit_nan_comparison() {
use qtty_core::units::length::{Kilometer, Meter};
let km_nan: Quantity<Kilometer> = Quantity::new(f64::NAN);
let m: Quantity<Meter> = Quantity::new(1000.0);
assert!(!(km_nan == m));
assert!(km_nan.partial_cmp(&m).is_none());
}
#[test]
fn ratio_basic_arithmetic() {
use qtty_core::units::dimensionless::Ratios;
let a = Ratios::new(3.0);
let b = Ratios::new(2.0);
assert_eq!((a + b).value(), 5.0);
assert_eq!((a - b).value(), 1.0);
assert_eq!((a * 2.0).value(), 6.0);
let raw: f64 = a / b;
assert!((raw - 1.5).abs() < 1e-12);
}
#[test]
#[cfg(feature = "std")]
fn ratio_exp_ln_roundtrip() {
use qtty_core::units::dimensionless::Ratios;
let x = Ratios::new(1.5);
let roundtrip = x.exp().ln();
assert!((roundtrip.value() - 1.5).abs() < 1e-12);
}
#[test]
#[cfg(feature = "std")]
fn ratio_powi() {
use qtty_core::units::dimensionless::Ratios;
let x = Ratios::new(2.0);
let result = x.powi(3);
assert!((result.value() - 8.0).abs() < 1e-12);
}
#[test]
#[cfg(feature = "std")]
fn ratio_powf() {
use qtty_core::units::dimensionless::{Ratio, Ratios};
use qtty_core::Quantity;
let base = Ratios::new(4.0);
let exp = Quantity::<Ratio>::new(0.5);
let result = base.powf(exp);
assert!((result.value() - 2.0).abs() < 1e-12);
}
#[test]
#[cfg(feature = "std")]
fn ratio_to_same_unit_division() {
use qtty_core::units::dimensionless::Ratios;
use qtty_core::units::length::Meters;
let a = Meters::new(10.0);
let b = Meters::new(4.0);
let r: Ratios = a.ratio_to(b);
assert!((r.value() - 2.5).abs() < 1e-12);
}