vyre-std 0.1.0

Vyre standard library: GPU DFA assembly pipeline, Aho-Corasick construction, and compositional arithmetic helpers
Documentation
use super::*;

#[test]
fn saturating_add_caps() {
    assert_eq!(saturating_add_u32(u32::MAX, 1), u32::MAX);
    assert_eq!(saturating_add_u8(200, 100), 255);
    assert_eq!(saturating_add_i32(i32::MAX, 1), i32::MAX);
    assert_eq!(saturating_add_i32(i32::MIN, -1), i32::MIN);
}

#[test]
fn saturating_sub_floors() {
    assert_eq!(saturating_sub_u32(0, 5), 0);
    assert_eq!(saturating_sub_i32(i32::MIN, 1), i32::MIN);
}

#[test]
fn saturating_mul_bounds() {
    assert_eq!(saturating_mul_i16(i16::MAX, 2), i16::MAX);
    assert_eq!(saturating_mul_i16(i16::MIN, 2), i16::MIN);
    assert_eq!(saturating_mul_u8(100, 3), 255);
}

#[test]
fn wrapping_add_and_sub() {
    assert_eq!(wrapping_add_u8(255, 1), 0);
    assert_eq!(wrapping_sub_u8(0, 1), 255);
    assert_eq!(wrapping_add_i8(i8::MAX, 1), i8::MIN);
}

#[test]
fn wrapping_mul_mod() {
    assert_eq!(wrapping_mul_u32(0xFFFF_FFFF, 2), 0xFFFF_FFFE);
}

#[test]
fn min_max_basic() {
    assert_eq!(min_u32(3, 5), 3);
    assert_eq!(max_u32(3, 5), 5);
}

#[test]
fn clamp_bounds() {
    assert_eq!(clamp_u32(5, 0, 10), 5);
    assert_eq!(clamp_u32(15, 0, 10), 10);
    assert_eq!(clamp_u32(0, 5, 10), 5);
}

#[test]
fn midpoint_no_overflow() {
    assert_eq!(midpoint_u32(u32::MAX - 1, u32::MAX), u32::MAX - 1);
    assert_eq!(midpoint_u32(0, 0), 0);
    assert_eq!(midpoint_u32(0, 10), 5);
}

#[test]
fn abs_diff_symmetric() {
    assert_eq!(abs_diff_u16(10, 3), 7);
    assert_eq!(abs_diff_u16(3, 10), 7);
}

#[test]
fn div_ceil_rounds_up() {
    assert_eq!(div_ceil_u32(10, 3), 4);
    assert_eq!(div_ceil_u32(9, 3), 3);
    assert_eq!(div_ceil_u32(0, 3), 0);
}

#[test]
fn div_round_half_up() {
    assert_eq!(div_round_u32(5, 2), 3);
    assert_eq!(div_round_u32(4, 2), 2);
    assert_eq!(div_round_u32(7, 3), 2);
}

/// Boundary suite for `div_round_uN` — audit L.1.1.
///
/// The pre-fix implementation `(numer + denom/2) / denom` overflowed
/// at `numer` near `MAX`, silently returning the wrong result. These
/// cases crash (panic) or return the wrong answer on the broken
/// version and pass on the widening-intermediate fix.
#[test]
fn div_round_boundary_no_overflow() {
    // u32 near MAX
    assert_eq!(div_round_u32(u32::MAX, 2), (u32::MAX / 2) + 1);
    // Hand-computed: (u32::MAX as u64 + 1) / 3 = 4_294_967_296 / 3
    assert_eq!(div_round_u32(u32::MAX, 3), 1_431_655_765);
    assert_eq!(div_round_u32(u32::MAX - 1, 2), u32::MAX / 2);
    assert_eq!(div_round_u32(u32::MAX, u32::MAX), 1);
    assert_eq!(div_round_u32(u32::MAX, 1), u32::MAX);
    // u64 near MAX
    assert_eq!(div_round_u64(u64::MAX, 2), (u64::MAX / 2) + 1);
    assert_eq!(div_round_u64(u64::MAX, u64::MAX), 1);
    assert_eq!(div_round_u64(u64::MAX, 1), u64::MAX);
    // u8 near MAX — the pre-fix overflowed at 255 + 128 = 383.
    assert_eq!(div_round_u8(u8::MAX, 2), 128);
    assert_eq!(div_round_u8(u8::MAX, 3), 85);
    assert_eq!(div_round_u8(u8::MAX, 255), 1);
    // u16 near MAX
    assert_eq!(div_round_u16(u16::MAX, 2), 32_768);
    assert_eq!(div_round_u16(u16::MAX, 3), 21_845);
    // Zero / small numerator
    assert_eq!(div_round_u32(0, 2), 0);
    assert_eq!(div_round_u32(1, 2), 1);
    assert_eq!(div_round_u32(0, 3), 0);
    assert_eq!(div_round_u64(1, 3), 0);
    // Round-up boundary
    assert_eq!(div_round_u32(3, 2), 2);
    assert_eq!(div_round_u32(2, 2), 1);
}

/// Exhaustive `(a, b)` for `div_round_u8` — small domain so we can
/// brute-force every pair.
#[test]
fn div_round_u8_exhaustive() {
    for a in 0u8..=u8::MAX {
        for b in 1u8..=u8::MAX {
            let expected = (u16::from(a) + u16::from(b) / 2) / u16::from(b);
            assert_eq!(
                u16::from(div_round_u8(a, b)),
                expected,
                "div_round_u8({a}, {b})"
            );
        }
    }
}

/// Release-mode panic proof for `clamp_uN` — audit L.1.2 requires the
/// stdlib `assert!` semantic, not `debug_assert!`, so inverted
/// bounds never silently return the wrong answer even in `--release`.
#[test]
#[should_panic(expected = "clamp_u32 called with lo > hi")]
fn clamp_u32_panics_on_inverted_bounds() {
    let _ = clamp_u32(5, 10, 0);
}

#[test]
#[should_panic(expected = "clamp_u8 called with lo > hi")]
fn clamp_u8_panics_on_inverted_bounds() {
    let _ = clamp_u8(5, 10, 0);
}

#[test]
#[should_panic(expected = "clamp_i32 called with lo > hi")]
fn clamp_i32_panics_on_inverted_bounds() {
    let _ = clamp_i32(5, 10, -10);
}

/// Signed arith helpers — audit L.1.7.
#[test]
fn signed_min_max_clamp() {
    assert_eq!(min_i8(-5, 3), -5);
    assert_eq!(max_i8(-5, 3), 3);
    assert_eq!(min_i32(i32::MIN, i32::MAX), i32::MIN);
    assert_eq!(max_i32(i32::MIN, i32::MAX), i32::MAX);
    assert_eq!(clamp_i32(0, -10, 10), 0);
    assert_eq!(clamp_i32(-100, -10, 10), -10);
    assert_eq!(clamp_i32(100, -10, 10), 10);
    assert_eq!(clamp_i64(i64::MIN, i64::MIN, i64::MAX), i64::MIN);
    assert_eq!(clamp_i64(i64::MAX, i64::MIN, i64::MAX), i64::MAX);
}

#[test]
fn signed_midpoint_no_overflow() {
    // i32::MIN + i32::MAX would overflow without widening. Sum is -1;
    // div_euclid(2) floors to -1.
    assert_eq!(midpoint_i32(i32::MIN, i32::MAX), -1);
    assert_eq!(midpoint_i32(i32::MIN, i32::MIN), i32::MIN);
    assert_eq!(midpoint_i32(i32::MAX, i32::MAX), i32::MAX);
    // Floor semantics (div_euclid rounds toward -inf).
    assert_eq!(midpoint_i8(-1, 0), -1);
    assert_eq!(midpoint_i8(0, 1), 0);
    // Sum is -1 in i128; div_euclid(2) floors toward -inf → -1.
    assert_eq!(midpoint_i64(i64::MIN, i64::MAX), -1);
}

#[test]
fn signed_abs_diff_full_range() {
    // Span the full i8 range: MAX - MIN = 255 (can't fit in i8).
    assert_eq!(abs_diff_i8(i8::MIN, i8::MAX), u8::MAX);
    assert_eq!(abs_diff_i8(i8::MAX, i8::MIN), u8::MAX);
    assert_eq!(abs_diff_i8(0, 0), 0);
    assert_eq!(abs_diff_i32(i32::MIN, i32::MAX), u32::MAX);
    assert_eq!(abs_diff_i64(i64::MIN, i64::MAX), u64::MAX);
    assert_eq!(abs_diff_i16(-10, 10), 20);
}

#[test]
fn lerp_endpoints() {
    assert!((lerp_f32(0.0, 10.0, 0.0) - 0.0).abs() < 1e-6);
    assert!((lerp_f32(0.0, 10.0, 1.0) - 10.0).abs() < 1e-6);
    assert!((lerp_f32(0.0, 10.0, 0.5) - 5.0).abs() < 1e-6);
    assert!((lerp_f64(1.0, 3.0, 0.5) - 2.0).abs() < 1e-12);
}

/// Documented lerp edge cases — audit L.1.8.
#[test]
fn lerp_edge_cases() {
    // NaN propagates.
    assert!(lerp_f32(f32::NAN, 1.0, 0.5).is_nan());
    assert!(lerp_f32(0.0, f32::NAN, 0.5).is_nan());
    assert!(lerp_f32(0.0, 1.0, f32::NAN).is_nan());
    // Extrapolation beyond [0, 1] is permitted.
    assert!((lerp_f32(0.0, 10.0, 2.0) - 20.0).abs() < 1e-5);
    assert!((lerp_f32(0.0, 10.0, -1.0) - -10.0).abs() < 1e-5);
    // Infinity arithmetic: the `a + (b - a) * t` form produces NaN for
    // (Inf, finite, 0.5) because `finite - Inf = -Inf` then
    // `Inf + (-Inf * 0.5) = Inf - Inf = NaN`. This is the documented
    // IEEE-754 behavior of this particular algebraic form; callers
    // that need `lerp(Inf, x, 0.5) == Inf` must pick a different
    // formulation. Record the observed semantics.
    assert!(lerp_f64(f64::INFINITY, 1.0, 0.5).is_nan());
    assert!(lerp_f64(f64::NEG_INFINITY, f64::INFINITY, 0.5).is_nan());
    // When (b - a) is well-defined, the infinity does survive.
    assert_eq!(lerp_f64(0.0, f64::INFINITY, 0.5), f64::INFINITY);
    assert_eq!(lerp_f64(0.0, f64::NEG_INFINITY, 0.5), f64::NEG_INFINITY);
}