qtty-core 0.7.1

Core types for zero-cost strongly-typed physical quantities.
Documentation
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (C) 2026 Vallés Puig, Ramon

//! Tests for the stable unit arithmetic layer (UnitDiv / UnitMul).

use qtty_core::units::length::{Kilometer, Meter};
use qtty_core::units::mass::Kilogram;
use qtty_core::units::time::Second;
use qtty_core::*;

// ─────────────────────────────────────────────────────────────────────────────
// U / U → raw scalar
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn same_unit_division_gives_raw_scalar_meter() {
    let a = Quantity::<Meter>::new(10.0);
    let b = Quantity::<Meter>::new(4.0);
    let ratio: f64 = a / b;
    assert!((ratio - 2.5).abs() < 1e-12);
}

#[test]
fn same_unit_division_gives_raw_scalar_second() {
    let a = Quantity::<Second>::new(100.0);
    let b = Quantity::<Second>::new(50.0);
    let ratio: f64 = a / b;
    assert!((ratio - 2.0).abs() < 1e-12);
}

#[test]
fn same_unit_division_gives_raw_scalar_kilogram() {
    let a = Quantity::<Kilogram>::new(6.0);
    let b = Quantity::<Kilogram>::new(3.0);
    let ratio: f64 = a / b;
    assert!((ratio - 2.0).abs() < 1e-12);
}

// ─────────────────────────────────────────────────────────────────────────────
// N / (N / D) → D
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn numerator_divided_by_per_gives_denominator() {
    let distance = Quantity::<Meter>::new(10.0);
    let velocity: Quantity<Per<Meter, Second>> = Quantity::new(2.0); // 2 m/s
    let time: Quantity<Second> = distance / velocity;
    assert!((time.value() - 5.0).abs() < 1e-12);
}

#[test]
fn kilometer_divided_by_velocity_gives_time() {
    let distance = Quantity::<Kilometer>::new(100.0);
    let velocity: Quantity<Per<Kilometer, Second>> = Quantity::new(50.0);
    let time: Quantity<Second> = distance / velocity;
    assert!((time.value() - 2.0).abs() < 1e-12);
}

// ─────────────────────────────────────────────────────────────────────────────
// N / D → Per<N, D>  (cross-unit division)
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn cross_unit_division_meter_second() {
    let d = Quantity::<Meter>::new(100.0);
    let t = Quantity::<Second>::new(10.0);
    let v: Quantity<Per<Meter, Second>> = d / t;
    assert!((v.value() - 10.0).abs() < 1e-12);
}

#[test]
fn cross_unit_division_kilometer_second() {
    let d = Quantity::<Kilometer>::new(50.0);
    let t = Quantity::<Second>::new(10.0);
    let v: Quantity<Per<Kilometer, Second>> = d / t;
    assert!((v.value() - 5.0).abs() < 1e-12);
}

#[test]
fn cross_unit_division_kilogram_meter() {
    let mass = Quantity::<Kilogram>::new(10.0);
    let length = Quantity::<Meter>::new(2.0);
    let density: Quantity<Per<Kilogram, Meter>> = mass / length;
    assert!((density.value() - 5.0).abs() < 1e-12);
}

// ─────────────────────────────────────────────────────────────────────────────
// Per<N, D> * D → N  and  D * Per<N, D> → N
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn per_times_denominator_gives_numerator() {
    let velocity: Quantity<Per<Meter, Second>> = Quantity::new(5.0);
    let time = Quantity::<Second>::new(10.0);
    let distance: Quantity<Meter> = velocity * time;
    assert!((distance.value() - 50.0).abs() < 1e-12);
}

#[test]
fn denominator_times_per_gives_numerator() {
    let velocity: Quantity<Per<Meter, Second>> = Quantity::new(5.0);
    let time = Quantity::<Second>::new(10.0);
    let distance: Quantity<Meter> = time * velocity;
    assert!((distance.value() - 50.0).abs() < 1e-12);
}

#[test]
fn per_times_denominator_commutative() {
    let rate: Quantity<Per<Kilometer, Second>> = Quantity::new(3.0);
    let t = Quantity::<Second>::new(7.0);
    let d1: Quantity<Kilometer> = rate * t;
    let d2: Quantity<Kilometer> = t * rate;
    assert!((d1.value() - d2.value()).abs() < 1e-12);
    assert!((d1.value() - 21.0).abs() < 1e-12);
}

// ─────────────────────────────────────────────────────────────────────────────
// A * B → Prod<A, B>  (plain multiplication fallback)
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn plain_multiplication_meter_meter() {
    let a = Quantity::<Meter>::new(4.0);
    let b = Quantity::<Meter>::new(5.0);
    let product: Quantity<Prod<Meter, Meter>> = a * b;
    assert!((product.value() - 20.0).abs() < 1e-12);
}

#[test]
fn plain_multiplication_meter_second() {
    let a = Quantity::<Meter>::new(3.0);
    let b = Quantity::<Second>::new(4.0);
    let product: Quantity<Prod<Meter, Second>> = a * b;
    assert!((product.value() - 12.0).abs() < 1e-12);
}

#[test]
fn plain_multiplication_converts_to_named_unit() {
    use qtty_core::units::area::SquareMeter;
    let a = Quantity::<Meter>::new(4.0);
    let b = Quantity::<Meter>::new(5.0);
    let area: Quantity<SquareMeter> = (a * b).to();
    assert!((area.value() - 20.0).abs() < 1e-12);
}

// ─────────────────────────────────────────────────────────────────────────────
// Custom unit tests: downstream users restore arithmetic via macros
// ─────────────────────────────────────────────────────────────────────────────

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum CustomLengthA {}
impl Unit for CustomLengthA {
    const RATIO: f64 = 1.0;
    type Dim = Length;
    const SYMBOL: &'static str = "cla";
}

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum CustomLengthB {}
impl Unit for CustomLengthB {
    const RATIO: f64 = 10.0;
    type Dim = Length;
    const SYMBOL: &'static str = "clb";
}

// Register custom units for arithmetic.
qtty_core::impl_unit_arithmetic_pairs!(CustomLengthA, CustomLengthB);

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum CustomLengthC {}
impl Unit for CustomLengthC {
    const RATIO: f64 = 1.7018;
    type Dim = Length;
    const SYMBOL: &'static str = "clc";
}

// Register a custom unit against selected built-ins without regenerating
// built-in/built-in impls.
qtty_core::impl_unit_arithmetic_pairs_between!(Meter, Kilometer; CustomLengthC);

#[test]
fn custom_unit_same_division_gives_raw_scalar() {
    let a = Quantity::<CustomLengthA>::new(10.0);
    let b = Quantity::<CustomLengthA>::new(5.0);
    let ratio: f64 = a / b;
    assert!((ratio - 2.0).abs() < 1e-12);
}

#[test]
fn custom_unit_cross_division() {
    let a = Quantity::<CustomLengthA>::new(100.0);
    let b = Quantity::<CustomLengthB>::new(10.0);
    let ratio: Quantity<Per<CustomLengthA, CustomLengthB>> = a / b;
    assert!((ratio.value() - 10.0).abs() < 1e-12);
}

#[test]
fn custom_unit_cross_multiplication() {
    let a = Quantity::<CustomLengthA>::new(3.0);
    let b = Quantity::<CustomLengthB>::new(4.0);
    let product: Quantity<Prod<CustomLengthA, CustomLengthB>> = a * b;
    assert!((product.value() - 12.0).abs() < 1e-12);
}

#[test]
fn custom_unit_self_multiplication() {
    let a = Quantity::<CustomLengthA>::new(3.0);
    let b = Quantity::<CustomLengthA>::new(4.0);
    let product: Quantity<Prod<CustomLengthA, CustomLengthA>> = a * b;
    assert!((product.value() - 12.0).abs() < 1e-12);
}

#[test]
fn custom_unit_between_group_division() {
    let distance = Quantity::<Meter>::new(10.0);
    let custom = Quantity::<CustomLengthC>::new(2.0);
    let ratio: Quantity<Per<Meter, CustomLengthC>> = distance / custom;
    assert!((ratio.value() - 5.0).abs() < 1e-12);
}

#[test]
fn custom_unit_between_group_multiplication() {
    let custom = Quantity::<CustomLengthC>::new(3.0);
    let distance = Quantity::<Kilometer>::new(4.0);
    let product: Quantity<Prod<CustomLengthC, Kilometer>> = custom * distance;
    assert!((product.value() - 12.0).abs() < 1e-12);
}

#[test]
fn custom_unit_between_group_self_multiplication() {
    let a = Quantity::<CustomLengthC>::new(3.0);
    let b = Quantity::<CustomLengthC>::new(4.0);
    let product: Quantity<Prod<CustomLengthC, CustomLengthC>> = a * b;
    assert!((product.value() - 12.0).abs() < 1e-12);
}

// ─────────────────────────────────────────────────────────────────────────────
// Trig via raw scalar from same-unit ratios
// ─────────────────────────────────────────────────────────────────────────────

#[test]
fn same_unit_ratio_asin() {
    let ratio: f64 = Quantity::<Meter>::new(1.0) / Quantity::<Meter>::new(2.0);
    let angle: Quantity<qtty_core::units::angular::Radian> = Quantity::new(ratio.asin());
    assert!((angle.value() - core::f64::consts::FRAC_PI_6).abs() < 1e-12);
}

#[test]
fn same_unit_ratio_acos() {
    let ratio: f64 = Quantity::<Meter>::new(1.0) / Quantity::<Meter>::new(2.0);
    let angle: Quantity<qtty_core::units::angular::Radian> = Quantity::new(ratio.acos());
    assert!((angle.value() - core::f64::consts::FRAC_PI_3).abs() < 1e-12);
}

#[test]
fn same_unit_ratio_atan() {
    let ratio: f64 = Quantity::<Meter>::new(1.0) / Quantity::<Meter>::new(1.0);
    let angle: Quantity<qtty_core::units::angular::Radian> = Quantity::new(ratio.atan());
    assert!((angle.value() - core::f64::consts::FRAC_PI_4).abs() < 1e-12);
}

// ─────────────────────────────────────────────────────────────────────────────
// Compile-time type assignment checks
// ─────────────────────────────────────────────────────────────────────────────

/// Compile-time check: U / U → raw scalar (f64)
fn _check_u_div_u_raw_scalar() {
    fn assert_f64(_: f64) {}
    assert_f64(Quantity::<Meter>::new(1.0) / Quantity::<Meter>::new(1.0));
    assert_f64(Quantity::<Second>::new(1.0) / Quantity::<Second>::new(1.0));
    assert_f64(Quantity::<Kilogram>::new(1.0) / Quantity::<Kilogram>::new(1.0));
}

/// Compile-time check: N / (N / D) → D
fn _check_n_div_per_n_d() {
    fn assert_second(_: Quantity<Second>) {}
    assert_second(Quantity::<Meter>::new(1.0) / Quantity::<Per<Meter, Second>>::new(1.0));
}

/// Compile-time check: N / D → Per<N, D>
fn _check_cross_div() {
    fn assert_per_m_s(_: Quantity<Per<Meter, Second>>) {}
    assert_per_m_s(Quantity::<Meter>::new(1.0) / Quantity::<Second>::new(1.0));
}

/// Compile-time check: Per<N, D> * D → N  and  D * Per<N, D> → N
fn _check_per_mul_recovery() {
    fn assert_meter(_: Quantity<Meter>) {}
    assert_meter(Quantity::<Per<Meter, Second>>::new(1.0) * Quantity::<Second>::new(1.0));
    assert_meter(Quantity::<Second>::new(1.0) * Quantity::<Per<Meter, Second>>::new(1.0));
}

/// Compile-time check: A * B → Prod<A, B>
fn _check_prod_fallback() {
    fn assert_prod_m_m(_: Quantity<Prod<Meter, Meter>>) {}
    assert_prod_m_m(Quantity::<Meter>::new(1.0) * Quantity::<Meter>::new(1.0));

    fn assert_prod_m_s(_: Quantity<Prod<Meter, Second>>) {}
    assert_prod_m_s(Quantity::<Meter>::new(1.0) * Quantity::<Second>::new(1.0));
}