g_math 0.4.2

Multi-domain fixed-point arithmetic with geometric extension: Lie groups, manifolds, ODE solvers, tensors, fiber bundles — zero-float, 0 ULP transcendentals
Documentation
//! Q8.24 realtime profile validation — mpmath 50-digit references.
//!
//! Run: GMATH_PROFILE=realtime GMATH_FRAC_BITS=24 cargo test --test q8_24_validation -- --nocapture
//!
//! Validates:
//! 1. Core transcendentals at Q8.24 precision (0 ULP target)
//! 2. sincos_wide for RoPE-class wide-angle inputs (Blocker 1)

// Only compile when FRAC_BITS=24 (Q8.24 format)
// On default Q16.16, the raw reference values would be wrong.
#![cfg(table_format = "q16_16")]

use g_math::fixed_point::imperative::FixedPoint;

fn fp(s: &str) -> FixedPoint { FixedPoint::from_str(s) }

// ============================================================================
// Q8.24 mpmath reference data (FRAC_BITS=24, SCALE=16777216)
// Generated by mpmath at 50-digit precision
// ============================================================================

const Q8_24_REFS: &[(&str, i32, i32, &str)] = &[
    ("0.5", 8388608, 27660953, "exp"),
    ("1.0", 16777216, 45605201, "exp"),
    ("-1.0", -16777216, 6171993, "exp"),
    ("2.0", 33554432, 123967790, "exp"),
    ("0.1", 1677722, 18541691, "exp"),
    ("0.5", 8388608, -11629080, "ln"),
    ("2.0", 33554432, 11629080, "ln"),
    ("3.0", 50331648, 18431656, "ln"),
    ("0.1", 1677722, -38630963, "ln"),  // ln(1677722/2^24), not ln(0.1)
    ("10.0", 167772160, 38630967, "ln"),
    ("0.5", 8388608, 8043426, "sin"),
    ("1.0", 16777216, 14117540, "sin"),
    ("2.0", 33554432, 15255479, "sin"),
    ("3.0", 50331648, 2367601, "sin"),
    ("0.1", 1677722, 1674927, "sin"),
    ("0.5", 8388608, 14723392, "cos"),
    ("1.0", 16777216, 9064768, "cos"),
    ("2.0", 33554432, -6981785, "cos"),
    ("3.0", 50331648, -16609318, "cos"),
    ("0.1", 1677722, 16693400, "cos"),
    ("0.5", 8388608, 11863283, "sqrt"),
    ("2.0", 33554432, 23726566, "sqrt"),
    ("4.0", 67108864, 33554432, "sqrt"),
    ("9.0", 150994944, 50331648, "sqrt"),
    ("0.1", 1677722, 5305422, "sqrt"),
    ("0.5", 8388608, 7778716, "atan"),
    ("1.0", 16777216, 13176795, "atan"),
    ("2.0", 33554432, 18574873, "atan"),
    ("-1.0", -16777216, -13176795, "atan"),
    ("0.1", 1677722, 1672163, "atan"),
];

// sincos_wide RoPE reference data (angles in Q32.32 i64)
const ROPE_REFS: &[(i64, i32, i32)] = &[
    // sin(10000), cos(10000) — exceeds Q8.24 range (±128) but sin/cos fit
    (42949672960000, -5127359, -15974516),
    // sin(100000), cos(100000)
    (429496729600000, 599765, -16766492),
    // sin(1000000), cos(1000000)
    (4294967296000000, -5871917, 15716093),
];

#[test]
fn test_q8_24_transcendentals_vs_mpmath() {
    // Check that FRAC_BITS is actually 24 (this test only makes sense for Q8.24)
    let one = FixedPoint::one();
    let one_raw = one.raw();
    if one_raw != 1i32 << 24 {
        println!("SKIP: FRAC_BITS is not 24 (one.raw() = {}, expected {})", one_raw, 1i32 << 24);
        println!("Run with: GMATH_PROFILE=realtime GMATH_FRAC_BITS=24 cargo test --test q8_24_validation");
        return; // Skip gracefully — don't fail, just not the right config
    }

    let mut failures = Vec::new();
    let mut max_ulp: i32 = 0;

    for &(input, _input_raw, expected_raw, func) in Q8_24_REFS {
        let input_fp = fp(input);
        let result = match func {
            "exp" => input_fp.try_exp(),
            "ln" => input_fp.try_ln(),
            "sin" => input_fp.try_sin(),
            "cos" => input_fp.try_cos(),
            "sqrt" => input_fp.try_sqrt(),
            "atan" => input_fp.try_atan(),
            _ => panic!("unknown function: {}", func),
        };
        match result {
            Ok(val) => {
                let got_raw = val.raw();
                let ulp = (got_raw - expected_raw).abs();
                if ulp > max_ulp { max_ulp = ulp; }
                if ulp > 1 {
                    failures.push(format!("  {}({}) = {} (expected {}), {} ULP",
                        func, input, got_raw, expected_raw, ulp));
                }
            }
            Err(e) => {
                failures.push(format!("  {}({}) = ERROR: {:?}", func, input, e));
            }
        }
    }

    println!("\n=== Q8.24 Realtime Profile ULP Validation ===");
    println!("Total test points: {}", Q8_24_REFS.len());
    println!("Max ULP: {}", max_ulp);
    println!("Failures (>1 ULP): {}", failures.len());
    for f in &failures {
        println!("{}", f);
    }

    assert!(failures.is_empty(),
        "Q8.24 profile: {} failures out of {} tests (max ULP: {})\n{}",
        failures.len(), Q8_24_REFS.len(), max_ulp, failures.join("\n"));
}

#[test]
fn test_sincos_wide_rope_angles() {
    // Check FRAC_BITS
    let one = FixedPoint::one();
    if one.raw() != 1i32 << 24 {
        println!("SKIP: sincos_wide RoPE test requires Q8.24 (GMATH_FRAC_BITS=24)");
        return;
    }

    println!("\n=== sincos_wide RoPE Validation ===");
    let mut max_ulp: i32 = 0;
    let mut failures = Vec::new();

    for &(angle_q32, expected_sin, expected_cos) in ROPE_REFS {
        let (sin_val, cos_val) = FixedPoint::sincos_wide(angle_q32);
        let sin_ulp = (sin_val.raw() - expected_sin).abs();
        let cos_ulp = (cos_val.raw() - expected_cos).abs();
        let ulp = sin_ulp.max(cos_ulp);
        if ulp > max_ulp { max_ulp = ulp; }

        let angle_int = angle_q32 >> 32;
        println!("  sincos_wide({}) → sin={}, cos={} (sin_ulp={}, cos_ulp={})",
            angle_int, sin_val.raw(), cos_val.raw(), sin_ulp, cos_ulp);

        if ulp > 1 {
            failures.push(format!("  angle={}: sin_ulp={}, cos_ulp={}", angle_int, sin_ulp, cos_ulp));
        }
    }

    println!("Max ULP: {}", max_ulp);
    assert!(failures.is_empty(),
        "sincos_wide: {} failures, max ULP {}\n{}", failures.len(), max_ulp, failures.join("\n"));
}