//! Configuration for skipping or changing the result for individual test cases (inputs) rather //! than ignoring entire tests. use core::f32; use CheckBasis::{Mpfr, Musl}; use libm::support::CastFrom; use {BaseName as Bn, Identifier as Id}; use crate::{BaseName, CheckBasis, CheckCtx, Float, Identifier, Int, TestResult}; /// Type implementing [`IgnoreCase`]. pub struct SpecialCase; /// ULP allowed to differ from the results returned by a test basis. #[allow(clippy::single_match)] pub fn default_ulp(ctx: &CheckCtx) -> u32 { // ULP compared to the infinite (MPFR) result. let mut ulp = match ctx.base_name { // Operations that require exact results. This list should correlate with what we // have documented at . Bn::Ceil | Bn::Copysign | Bn::Fabs | Bn::Fdim | Bn::Floor | Bn::Fma | Bn::Fmax | Bn::Fmaximum | Bn::FmaximumNum | Bn::Fmin | Bn::Fminimum | Bn::FminimumNum | Bn::Fmod | Bn::Frexp | Bn::Ilogb | Bn::Ldexp | Bn::Modf | Bn::Nextafter | Bn::Remainder | Bn::Remquo | Bn::Rint | Bn::Round | Bn::Roundeven | Bn::Scalbn | Bn::Sqrt | Bn::Trunc => 0, // Operations that aren't required to be exact, but our implementations are. Bn::Cbrt => 0, // Bessel functions have large inaccuracies. Bn::J0 | Bn::J1 | Bn::Y0 | Bn::Y1 | Bn::Jn | Bn::Yn => 8_000_000, // For all other operations, specify our implementation's worst case precision. Bn::Acos => 1, Bn::Acosh => 4, Bn::Asin => 1, Bn::Asinh => 2, Bn::Atan => 1, Bn::Atan2 => 2, Bn::Atanh => 2, Bn::Cos => 1, Bn::Cosh => 1, Bn::Erf => 1, Bn::Erfc => 4, Bn::Exp => 1, Bn::Exp10 => 6, Bn::Exp2 => 1, Bn::Expm1 => 1, Bn::Hypot => 1, Bn::Lgamma | Bn::LgammaR => 16, Bn::Log => 1, Bn::Log10 => 1, Bn::Log1p => 1, Bn::Log2 => 1, Bn::Pow => 1, Bn::Sin => 1, Bn::Sincos => 1, Bn::Sinh => 2, Bn::Tan => 1, Bn::Tanh => 2, // tgammaf has higher accuracy than tgamma. Bn::Tgamma if ctx.fn_ident != Id::Tgamma => 1, Bn::Tgamma => 20, }; // There are some cases where musl's approximation is less accurate than ours. For these // cases, increase the ULP. if ctx.basis == Musl { match ctx.base_name { Bn::Cosh => ulp = 2, Bn::Exp10 if usize::BITS < 64 => ulp = 4, Bn::Lgamma | Bn::LgammaR => ulp = 400, Bn::Tanh => ulp = 4, _ => (), } match ctx.fn_ident { Id::Cbrt => ulp = 2, // FIXME(#401): musl has an incorrect result here. Id::Fdim => ulp = 2, Id::Sincosf => ulp = 500, Id::Tgamma => ulp = 20, _ => (), } } if cfg!(target_arch = "x86") { match ctx.fn_ident { // Input `fma(0.999999999999999, 1.0000000000000013, 0.0) = 1.0000000000000002` is // incorrect on i586 and i686. Id::Fma => ulp = 1, _ => (), } } // In some cases, our implementation is less accurate than musl on i586. if cfg!(x86_no_sse) { match ctx.fn_ident { // FIXME(#401): these need to be correctly rounded but are not. Id::Fmaf => ulp = 1, Id::Fdim => ulp = 1, Id::Round => ulp = 1, Id::Asinh => ulp = 3, Id::Asinhf => ulp = 3, Id::Cbrt => ulp = 1, Id::Exp10 | Id::Exp10f => ulp = 1_000_000, Id::Exp2 | Id::Exp2f => ulp = 10_000_000, Id::Log1p | Id::Log1pf => ulp = 2, Id::Tan => ulp = 2, _ => (), } } ulp } /// Result of checking for possible overrides. #[derive(Debug, Default)] pub enum CheckAction { /// The check should pass. Default case. #[default] AssertSuccess, /// Override the ULP for this check. AssertWithUlp(u32), /// Failure is expected, ensure this is the case (xfail). Takes a contxt string to help trace /// back exactly why we expect this to fail. AssertFailure(&'static str), /// The override somehow validated the result, here it is. Custom(TestResult), /// Disregard the output. Skip, } /// Don't run further validation on this test case. const SKIP: CheckAction = CheckAction::Skip; /// Return this to skip checks on a test that currently fails but shouldn't. Takes a description /// of context. const XFAIL: fn(&'static str) -> CheckAction = CheckAction::AssertFailure; /// Indicates that we expect a test to fail but we aren't asserting that it does (e.g. some results /// within a range do actually pass). /// /// Same as `SKIP`, just indicates we have something to eventually fix. const XFAIL_NOCHECK: CheckAction = CheckAction::Skip; /// By default, all tests should pass. const DEFAULT: CheckAction = CheckAction::AssertSuccess; /// Allow overriding the outputs of specific test cases. /// /// There are some cases where we want to xfail specific cases or handle certain inputs /// differently than the rest of calls to `validate`. This provides a hook to do that. /// /// If `None` is returned, checks will proceed as usual. If `Some(result)` is returned, checks /// are skipped and the provided result is returned instead. /// /// This gets implemented once per input type, then the functions provide further filtering /// based on function name and values. /// /// `ulp` can also be set to adjust the ULP for that specific test, even if `None` is still /// returned. pub trait MaybeOverride { fn check_float( _input: Input, _actual: F, _expected: F, _ctx: &CheckCtx, ) -> CheckAction { DEFAULT } fn check_int(_input: Input, _actual: I, _expected: I, _ctx: &CheckCtx) -> CheckAction { DEFAULT } } #[cfg(f16_enabled)] impl MaybeOverride<(f16,)> for SpecialCase {} impl MaybeOverride<(f32,)> for SpecialCase { fn check_float(input: (f32,), actual: F, expected: F, ctx: &CheckCtx) -> CheckAction { if ctx.base_name == BaseName::Expm1 && !input.0.is_infinite() && input.0 > 80.0 && actual.is_infinite() && !expected.is_infinite() { // we return infinity but the number is representable if ctx.basis == CheckBasis::Musl { return XFAIL_NOCHECK; } return XFAIL("expm1 representable numbers"); } if cfg!(x86_no_sse) && ctx.base_name == BaseName::Exp2 && !expected.is_infinite() && actual.is_infinite() { // We return infinity when there is a representable value. Test input: 127.97238 return XFAIL("586 exp2 representable numbers"); } if ctx.base_name == BaseName::Sinh && input.0.abs() > 80.0 && actual.is_nan() { // we return some NaN that should be real values or infinite if ctx.basis == CheckBasis::Musl { return XFAIL_NOCHECK; } return XFAIL("sinh unexpected NaN"); } if (ctx.base_name == BaseName::Lgamma || ctx.base_name == BaseName::LgammaR) && input.0 > 4e36 && expected.is_infinite() && !actual.is_infinite() { // This result should saturate but we return a finite value. return XFAIL_NOCHECK; } if ctx.base_name == BaseName::J0 && input.0 < -1e34 { // Errors get huge close to -inf return XFAIL_NOCHECK; } unop_common(input, actual, expected, ctx) } fn check_int(input: (f32,), actual: I, expected: I, ctx: &CheckCtx) -> CheckAction { // On MPFR for lgammaf_r, we set -1 as the integer result for negative infinity but MPFR // sets +1 if ctx.basis == CheckBasis::Mpfr && ctx.base_name == BaseName::LgammaR && input.0 == f32::NEG_INFINITY && actual.abs() == expected.abs() { return XFAIL("lgammar integer result"); } DEFAULT } } impl MaybeOverride<(f64,)> for SpecialCase { fn check_float(input: (f64,), actual: F, expected: F, ctx: &CheckCtx) -> CheckAction { if cfg!(x86_no_sse) && (ctx.base_name == BaseName::Rint || ctx.base_name == BaseName::Roundeven) && (expected - actual).abs() <= F::ONE && (expected - actual).abs() > F::ZERO { // Our rounding mode is incorrect. return XFAIL("i586 rint rounding mode"); } if cfg!(x86_no_sse) && (ctx.fn_ident == Identifier::Exp10 || ctx.fn_ident == Identifier::Exp2) { // FIXME: i586 has very imprecise results with ULP > u32::MAX for these // operations so we can't reasonably provide a limit. return XFAIL_NOCHECK; } if ctx.base_name == BaseName::J0 && input.0 < -1e300 { // Errors get huge close to -inf return XFAIL_NOCHECK; } // maybe_check_nan_bits(actual, expected, ctx) unop_common(input, actual, expected, ctx) } fn check_int(input: (f64,), actual: I, expected: I, ctx: &CheckCtx) -> CheckAction { // On MPFR for lgamma_r, we set -1 as the integer result for negative infinity but MPFR // sets +1 if ctx.basis == CheckBasis::Mpfr && ctx.base_name == BaseName::LgammaR && input.0 == f64::NEG_INFINITY && actual.abs() == expected.abs() { return XFAIL("lgammar integer result"); } DEFAULT } } #[cfg(f128_enabled)] impl MaybeOverride<(f128,)> for SpecialCase {} // F1 and F2 are always the same type, this is just to please generics fn unop_common( input: (F1,), actual: F2, expected: F2, ctx: &CheckCtx, ) -> CheckAction { if ctx.base_name == BaseName::Acosh && input.0 < F1::NEG_ONE && !(expected.is_nan() && actual.is_nan()) { // acoshf is undefined for x <= 1.0, but we return a random result at lower values. if ctx.basis == CheckBasis::Musl { return XFAIL_NOCHECK; } return XFAIL("acoshf undefined"); } if (ctx.base_name == BaseName::Lgamma || ctx.base_name == BaseName::LgammaR) && input.0 < F1::ZERO && !input.0.is_infinite() { // loggamma should not be defined for x < 0, yet we both return results return XFAIL_NOCHECK; } // fabs and copysign must leave NaNs untouched. if ctx.base_name == BaseName::Fabs && input.0.is_nan() { // LLVM currently uses x87 instructions which quieten signalling NaNs to handle the i686 // `extern "C"` `f32`/`f64` return ABI. // LLVM issue // Rust issue if cfg!(target_arch = "x86") && ctx.basis == CheckBasis::Musl && actual.is_nan() { return XFAIL_NOCHECK; } // MPFR only has one NaN bitpattern; allow the default `.is_nan()` checks to validate. if ctx.basis == CheckBasis::Mpfr { return DEFAULT; } // abs and copysign require signaling NaNs to be propagated, so verify bit equality. if actual.biteq(expected) { return CheckAction::Custom(Ok(())); } else { return CheckAction::Custom(Err(anyhow::anyhow!("NaNs have different bitpatterns"))); } } DEFAULT } #[cfg(f16_enabled)] impl MaybeOverride<(f16, f16)> for SpecialCase { fn check_float( input: (f16, f16), actual: F, expected: F, ctx: &CheckCtx, ) -> CheckAction { binop_common(input, actual, expected, ctx) } } impl MaybeOverride<(f32, f32)> for SpecialCase { fn check_float( input: (f32, f32), actual: F, expected: F, ctx: &CheckCtx, ) -> CheckAction { binop_common(input, actual, expected, ctx) } } impl MaybeOverride<(f64, f64)> for SpecialCase { fn check_float( input: (f64, f64), actual: F, expected: F, ctx: &CheckCtx, ) -> CheckAction { binop_common(input, actual, expected, ctx) } } #[cfg(f128_enabled)] impl MaybeOverride<(f128, f128)> for SpecialCase { fn check_float( input: (f128, f128), actual: F, expected: F, ctx: &CheckCtx, ) -> CheckAction { binop_common(input, actual, expected, ctx) } } // F1 and F2 are always the same type, this is just to please generics fn binop_common( input: (F1, F1), actual: F2, expected: F2, ctx: &CheckCtx, ) -> CheckAction { // MPFR only has one NaN bitpattern; skip tests in cases where the first argument would take // the sign of a NaN second argument. The default NaN checks cover other cases. if ctx.base_name == BaseName::Copysign && ctx.basis == CheckBasis::Mpfr && input.1.is_nan() { return SKIP; } // FIXME(#939): this should not be skipped, there is a bug in our implementationi. if ctx.base_name == BaseName::FmaximumNum && ctx.basis == CheckBasis::Mpfr && ((input.0.is_nan() && actual.is_nan() && expected.is_nan()) || input.1.is_nan()) { return XFAIL_NOCHECK; } /* FIXME(#439): our fmin and fmax do not compare signed zeros */ if ctx.base_name == BaseName::Fmin && input.0.biteq(F1::NEG_ZERO) && input.1.biteq(F1::ZERO) && expected.biteq(F2::NEG_ZERO) && actual.biteq(F2::ZERO) { return XFAIL("fmin signed zeroes"); } if ctx.base_name == BaseName::Fmax && input.0.biteq(F1::NEG_ZERO) && input.1.biteq(F1::ZERO) && expected.biteq(F2::ZERO) && actual.biteq(F2::NEG_ZERO) { return XFAIL("fmax signed zeroes"); } // Musl propagates NaNs if one is provided as the input, but we return the other input. if (ctx.base_name == BaseName::Fmax || ctx.base_name == BaseName::Fmin) && ctx.basis == Musl && (input.0.is_nan() ^ input.1.is_nan()) && expected.is_nan() { return XFAIL("fmax/fmin musl NaN"); } DEFAULT } impl MaybeOverride<(i32, f32)> for SpecialCase { fn check_float( input: (i32, f32), actual: F, expected: F, ctx: &CheckCtx, ) -> CheckAction { // `ynf(213, 109.15641) = -inf` with our library, should be finite. if ctx.basis == Mpfr && ctx.base_name == BaseName::Yn && input.0 > 200 && !expected.is_infinite() && actual.is_infinite() { return XFAIL("ynf infinity mismatch"); } int_float_common(input, actual, expected, ctx) } } impl MaybeOverride<(i32, f64)> for SpecialCase { fn check_float( input: (i32, f64), actual: F, expected: F, ctx: &CheckCtx, ) -> CheckAction { int_float_common(input, actual, expected, ctx) } } fn int_float_common( input: (i32, F1), actual: F2, expected: F2, ctx: &CheckCtx, ) -> CheckAction { if ctx.basis == Mpfr && (ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn) && input.1 == F1::NEG_INFINITY && actual == F2::ZERO && expected == F2::ZERO { return XFAIL("we disagree with MPFR on the sign of zero"); } // Values near infinity sometimes get cut off for us. `ynf(681, 509.90924) = -inf` but should // be -3.2161271e38. if ctx.basis == Musl && ctx.fn_ident == Identifier::Ynf && !expected.is_infinite() && actual.is_infinite() && (expected.abs().to_bits().abs_diff(actual.abs().to_bits()) < F2::Int::cast_from(10_000_000u32)) { return XFAIL_NOCHECK; } // Our bessel functions blow up with large N values if ctx.basis == Musl && (ctx.base_name == BaseName::Jn || ctx.base_name == BaseName::Yn) { if cfg!(x86_no_sse) { // Precision is especially bad on i586, not worth checking. return XFAIL_NOCHECK; } if input.0 > 4000 { return XFAIL_NOCHECK; } else if input.0 > 100 { return CheckAction::AssertWithUlp(1_000_000); } } DEFAULT } #[cfg(f16_enabled)] impl MaybeOverride<(f16, i32)> for SpecialCase {} impl MaybeOverride<(f32, i32)> for SpecialCase {} impl MaybeOverride<(f64, i32)> for SpecialCase {} #[cfg(f128_enabled)] impl MaybeOverride<(f128, i32)> for SpecialCase {} impl MaybeOverride<(f32, f32, f32)> for SpecialCase {} impl MaybeOverride<(f64, f64, f64)> for SpecialCase {} #[cfg(f128_enabled)] impl MaybeOverride<(f128, f128, f128)> for SpecialCase {}