MathUtils 1.0.1

A high-fidelity Rust mirror of the JavaScript (ECMAScript) Math built-in object.
Documentation
use MathUtils::*;

#[test]
fn test_constants_accuracy() {
    assert_eq!(Math::E, std::f64::consts::E);
    assert_eq!(Math::LN10, std::f64::consts::LN_10);
    assert_eq!(Math::LN_10, std::f64::consts::LN_10);
    assert_eq!(Math::LN2, std::f64::consts::LN_2);
    assert_eq!(Math::LN_2, std::f64::consts::LN_2);
    assert_eq!(Math::LOG10E, std::f64::consts::LOG10_E);
    assert_eq!(Math::LOG10_E, std::f64::consts::LOG10_E);
    assert_eq!(Math::LOG2E, std::f64::consts::LOG2_E);
    assert_eq!(Math::LOG2_E, std::f64::consts::LOG2_E);
    assert_eq!(Math::PI, std::f64::consts::PI);
    assert_eq!(Math::SQRT1_2, std::f64::consts::FRAC_1_SQRT_2);
    assert_eq!(Math::SQRT_1_2, std::f64::consts::FRAC_1_SQRT_2);
    assert_eq!(Math::SQRT2, std::f64::consts::SQRT_2);
    assert_eq!(Math::SQRT_2, std::f64::consts::SQRT_2);

    // Root exports
    assert_eq!(E, Math::E);
    assert_eq!(PI, Math::PI);
    assert_eq!(LN10, Math::LN10);
}

#[test]
fn test_abs() {
    assert_eq!(Math::abs(5.0), 5.0);
    assert_eq!(Math::abs(-5.0), 5.0);
    assert_eq!(Math::abs(0.0), 0.0);
    assert!(Math::abs(0.0).is_sign_positive());
    assert_eq!(Math::abs(-0.0), 0.0);
    assert!(Math::abs(-0.0).is_sign_positive());
    assert_eq!(Math::abs(-f64::INFINITY), f64::INFINITY);
    assert!(Math::abs(f64::NAN).is_nan());
}

#[test]
fn test_round_ecmascript_semantics() {
    // Ties round toward +infinity in ECMAScript:
    assert_eq!(Math::round(0.5), 1.0);
    assert_eq!(Math::round(1.5), 2.0);
    assert_eq!(Math::round(2.5), 3.0);
    assert_eq!(Math::round(3.5), 4.0);

    // Negative ties:
    assert_eq!(Math::round(-0.5), 0.0);
    assert!(Math::round(-0.5).is_sign_negative(), "Math.round(-0.5) must be -0.0");
    assert_eq!(Math::round(-1.5), -1.0);
    assert_eq!(Math::round(-2.5), -2.0);
    assert_eq!(Math::round(-3.5), -3.0);

    // Zero signs
    assert_eq!(Math::round(0.0), 0.0);
    assert!(Math::round(0.0).is_sign_positive());
    assert_eq!(Math::round(-0.0), 0.0);
    assert!(Math::round(-0.0).is_sign_negative(), "Math.round(-0.0) must be -0.0");

    // Strict [-0.5, 0.0)
    assert_eq!(Math::round(-0.4), 0.0);
    assert!(Math::round(-0.4).is_sign_negative());
    assert_eq!(Math::round(-0.6), -1.0);

    // Precision boundary:
    assert_eq!(Math::round(0.49999999999999994), 0.0);
    assert_eq!(Math::round(-0.49999999999999994), -0.0);

    // Large floats >= 2^52
    assert_eq!(Math::round(4503599627370497.0), 4503599627370497.0);
    assert_eq!(Math::round(-4503599627370497.0), -4503599627370497.0);

    // Special floats
    assert!(Math::round(f64::NAN).is_nan());
    assert_eq!(Math::round(f64::INFINITY), f64::INFINITY);
    assert_eq!(Math::round(f64::NEG_INFINITY), f64::NEG_INFINITY);
}

#[test]
fn test_sign_ecmascript_semantics() {
    assert_eq!(Math::sign(3.0), 1.0);
    assert_eq!(Math::sign(-3.0), -1.0);
    assert_eq!(Math::sign(0.0), 0.0);
    assert!(Math::sign(0.0).is_sign_positive());
    assert_eq!(Math::sign(-0.0), 0.0);
    assert!(Math::sign(-0.0).is_sign_negative());
    assert_eq!(Math::sign(f64::INFINITY), 1.0);
    assert_eq!(Math::sign(f64::NEG_INFINITY), -1.0);
    assert!(Math::sign(f64::NAN).is_nan());
}

#[test]
fn test_pow_ecmascript_semantics() {
    // Exponent 0 with NaN base
    assert_eq!(Math::pow(f64::NAN, 0.0), 1.0);
    assert_eq!(Math::pow(f64::NAN, -0.0), 1.0);

    // Base 1 with Infinite exponent
    assert!(Math::pow(1.0, f64::INFINITY).is_nan());
    assert!(Math::pow(1.0, f64::NEG_INFINITY).is_nan());
    assert!(Math::pow(-1.0, f64::INFINITY).is_nan());
    assert!(Math::pow(-1.0, f64::NEG_INFINITY).is_nan());

    // Negative zero base
    assert_eq!(Math::pow(0.0, 1.0), 0.0);
    assert!(Math::pow(0.0, 1.0).is_sign_positive());

    let res_neg0_1 = Math::pow(-0.0, 1.0);
    assert_eq!(res_neg0_1, 0.0);
    assert!(res_neg0_1.is_sign_negative());

    let res_neg0_2 = Math::pow(-0.0, 2.0);
    assert_eq!(res_neg0_2, 0.0);
    assert!(res_neg0_2.is_sign_positive());

    assert_eq!(Math::pow(-0.0, -1.0), f64::NEG_INFINITY);
    assert_eq!(Math::pow(-0.0, -2.0), f64::INFINITY);

    // Negative infinity base
    assert_eq!(Math::pow(f64::NEG_INFINITY, 1.0), f64::NEG_INFINITY);
    assert_eq!(Math::pow(f64::NEG_INFINITY, 2.0), f64::INFINITY);
    let res_neginf_neg1 = Math::pow(f64::NEG_INFINITY, -1.0);
    assert_eq!(res_neginf_neg1, 0.0);
    assert!(res_neginf_neg1.is_sign_negative());

    let res_neginf_neg2 = Math::pow(f64::NEG_INFINITY, -2.0);
    assert_eq!(res_neginf_neg2, 0.0);
    assert!(res_neginf_neg2.is_sign_positive());

    // Fractional power of negative base
    assert!(Math::pow(-2.0, 0.5).is_nan());

    // Standard cases
    assert_eq!(Math::pow(2.0, 3.0), 8.0);
    assert_eq!(Math::pow(2.0, -3.0), 0.125);
    assert_eq!(Math::pow(-2.0, 3.0), -8.0);
    assert_eq!(Math::pow(-2.0, 2.0), 4.0);
}

#[test]
fn test_hypot_ecmascript_semantics() {
    assert_eq!(Math::hypot(&[]), 0.0);
    assert_eq!(Math::hypot(&[3.0, 4.0]), 5.0);
    assert_eq!(Math::hypot(&[1.0, 2.0, 2.0]), 3.0);
    assert_eq!(Math::hypot2(3.0, 4.0), 5.0);

    // Infinity overrides NaN according to JS spec!
    assert_eq!(Math::hypot(&[f64::INFINITY, f64::NAN]), f64::INFINITY);
    assert_eq!(Math::hypot(&[f64::NAN, f64::NEG_INFINITY]), f64::INFINITY);
    assert_eq!(Math::hypot2(f64::INFINITY, f64::NAN), f64::INFINITY);

    // Large and small scaling
    assert!((Math::hypot(&[1e300, 1e300]) - 1.4142135623730952e300).abs() < 1e285);
    assert!((Math::hypot(&[1e-300, 1e-300]) - 1.4142135623730952e-300).abs() < 1e-315);

    // Macros
    assert_eq!(math_hypot!(), 0.0);
    assert_eq!(math_hypot!(3.0, 4.0), 5.0);
    assert_eq!(math_hypot!(1.0, 2.0, 2.0), 3.0);
}

#[test]
fn test_max_and_min_ecmascript_semantics() {
    // Empty
    assert_eq!(Math::max(&[]), f64::NEG_INFINITY);
    assert_eq!(Math::min(&[]), f64::INFINITY);

    // Zeros
    let max_zeros = Math::max(&[-0.0, 0.0]);
    assert_eq!(max_zeros, 0.0);
    assert!(max_zeros.is_sign_positive());

    let min_zeros = Math::min(&[-0.0, 0.0]);
    assert_eq!(min_zeros, 0.0);
    assert!(min_zeros.is_sign_negative());

    let max2_zeros = Math::max2(-0.0, 0.0);
    assert_eq!(max2_zeros, 0.0);
    assert!(max2_zeros.is_sign_positive());

    let min2_zeros = Math::min2(0.0, -0.0);
    assert_eq!(min2_zeros, 0.0);
    assert!(min2_zeros.is_sign_negative());

    // NaN
    assert!(Math::max(&[1.0, f64::NAN, 2.0]).is_nan());
    assert!(Math::min(&[1.0, f64::NAN, 2.0]).is_nan());

    // Macros
    assert_eq!(math_max!(), f64::NEG_INFINITY);
    assert_eq!(math_max!(5.0), 5.0);
    assert_eq!(math_max!(1.0, 10.0, 3.0), 10.0);

    assert_eq!(math_min!(), f64::INFINITY);
    assert_eq!(math_min!(5.0), 5.0);
    assert_eq!(math_min!(1.0, -10.0, 3.0), -10.0);
}

#[test]
fn test_clz32_and_imul() {
    assert_eq!(Math::clz32(0.0), 32);
    assert_eq!(Math::clz32(1.0), 31);
    assert_eq!(Math::clz32(-1.0), 0);
    assert_eq!(Math::clz32(3.5), 30);
    assert_eq!(Math::clz32(-3.5), 0);
    assert_eq!(Math::clz32(f64::NAN), 32);
    assert_eq!(Math::clz32(f64::INFINITY), 32);
    assert_eq!(Math::clz32(f64::NEG_INFINITY), 32);
    assert_eq!(Math::clz32(4294967295.0), 0);
    assert_eq!(Math::clz32(4294967296.0), 32);
    assert_eq!(Math::clz32(4294967297.0), 31);
    assert_eq!(Math::clz32(-4294967295.0), 31);
    assert_eq!(Math::clz32(1e20), 1);
    assert_eq!(Math::clz32(-1e20), 0);

    // Direct integer types
    assert_eq!(Math::clz32(0u32), 32);
    assert_eq!(Math::clz32(1u32), 31);
    assert_eq!(Math::clz32(-1i32), 0);

    // imul
    assert_eq!(Math::imul(2, 4), 8);
    assert_eq!(Math::imul(-1, 8), -8);
    assert_eq!(Math::imul(-2, -2), 4);
    assert_eq!(Math::imul(0xffff_ffffu32, 5), -5);
    assert_eq!(Math::imul(f64::NAN, 5.0), 0);
    assert_eq!(Math::imul(f64::INFINITY, 5.0), 0);
    assert_eq!(Math::imul(0.5, 2.5), 0);
}

#[test]
fn test_fround_and_f16round() {
    assert_eq!(Math::fround(1.337), 1.3370000123977661);
    assert_eq!(Math::fround(0.0), 0.0);
    assert!(Math::fround(0.0).is_sign_positive());
    assert!(Math::fround(-0.0).is_sign_negative());
    assert!(Math::fround(f64::NAN).is_nan());
    assert_eq!(Math::fround(f64::INFINITY), f64::INFINITY);

    assert_eq!(Math::f16round(1.337), 1.3369140625);
    assert_eq!(Math::f16round(std::f64::consts::PI), 3.140625);
    assert_eq!(Math::f16round(65504.0), 65504.0);
    assert_eq!(Math::f16round(65519.0), 65504.0);
    assert_eq!(Math::f16round(65520.0), f64::INFINITY);
    assert_eq!(Math::f16round(-65520.0), f64::NEG_INFINITY);
}

#[test]
fn test_trigonometric_and_logarithmic() {
    assert_eq!(Math::sin(0.0), 0.0);
    assert_eq!(Math::cos(0.0), 1.0);
    assert_eq!(Math::tan(0.0), 0.0);

    assert_eq!(Math::asin(0.0), 0.0);
    assert_eq!(Math::acos(1.0), 0.0);
    assert_eq!(Math::atan(0.0), 0.0);

    assert_eq!(Math::atan2(0.0, 1.0), 0.0);
    assert_eq!(Math::atan2(0.0, -0.0), std::f64::consts::PI);
    assert_eq!(Math::atan2(-0.0, -0.0), -std::f64::consts::PI);

    assert_eq!(Math::sinh(0.0), 0.0);
    assert_eq!(Math::cosh(0.0), 1.0);
    assert_eq!(Math::tanh(0.0), 0.0);

    assert_eq!(Math::asinh(0.0), 0.0);
    assert_eq!(Math::acosh(1.0), 0.0);
    assert_eq!(Math::atanh(0.0), 0.0);

    assert_eq!(Math::exp(0.0), 1.0);
    assert_eq!(Math::expm1(0.0), 0.0);

    assert_eq!(Math::log(std::f64::consts::E), 1.0);
    assert_eq!(Math::log1p(0.0), 0.0);
    assert_eq!(Math::log_1p(0.0), 0.0);
    assert_eq!(Math::log10(100.0), 2.0);
    assert_eq!(Math::log_10(100.0), 2.0);
    assert_eq!(Math::log2(8.0), 3.0);
    assert_eq!(Math::log_2(8.0), 3.0);

    assert_eq!(Math::sqrt(9.0), 3.0);
    assert_eq!(Math::cbrt(27.0), 3.0);
    assert_eq!(Math::cbrt(-8.0), -2.0);

    assert_eq!(Math::floor(3.9), 3.0);
    assert_eq!(Math::ceil(3.1), 4.0);
    assert_eq!(Math::trunc(3.9), 3.0);
    assert_eq!(Math::trunc(-3.9), -3.0);
}

#[test]
fn test_sum_precise_cancellation() {
    let list = [1e16, 1.0, -1e16];
    assert_eq!(Math::sumPrecise(&list), 1.0);
    assert_eq!(Math::sum_precise(&list), 1.0);
}