Replace libm_test::{Float, Int} with libm::{Float, Int}
This involves moving some things from full generic implementations (e.g.
`impl<F: Float> SomeTrait for F { /* ... */ }` to generic functions and
macros to implement traits that call them, due to orphan rule violations
after `Float` became a not-in-crate trait.
`Hex` was moved to `test_traits` so we can eliminate `num_traits`.
This commit is contained in:
committed by
Trevor Gross
parent
4b12a8404f
commit
dbf8a4ebe5
@@ -1,13 +1,12 @@
|
||||
pub mod gen;
|
||||
#[cfg(feature = "test-multiprecision")]
|
||||
pub mod mpfloat;
|
||||
mod num_traits;
|
||||
mod precision;
|
||||
mod test_traits;
|
||||
|
||||
pub use num_traits::{Float, Hex, Int};
|
||||
pub use libm::support::{Float, Int};
|
||||
pub use precision::{MaybeOverride, SpecialCase, multiprec_allowed_ulp, musl_allowed_ulp};
|
||||
pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, TupleCall};
|
||||
pub use test_traits::{CheckBasis, CheckCtx, CheckOutput, GenerateInput, Hex, TupleCall};
|
||||
|
||||
/// Result type for tests is usually from `anyhow`. Most times there is no success value to
|
||||
/// propagate.
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::{MaybeOverride, SpecialCase, TestResult};
|
||||
|
||||
/// Common types and methods for floating point numbers.
|
||||
pub trait Float: Copy + fmt::Display + fmt::Debug + PartialEq<Self> {
|
||||
type Int: Int<OtherSign = Self::SignedInt, Unsigned = Self::Int>;
|
||||
type SignedInt: Int + Int<OtherSign = Self::Int, Unsigned = Self::Int>;
|
||||
|
||||
const ZERO: Self;
|
||||
const ONE: Self;
|
||||
|
||||
/// The bitwidth of the float type
|
||||
const BITS: u32;
|
||||
|
||||
/// The bitwidth of the significand
|
||||
const SIGNIFICAND_BITS: u32;
|
||||
|
||||
/// The bitwidth of the exponent
|
||||
const EXPONENT_BITS: u32 = Self::BITS - Self::SIGNIFICAND_BITS - 1;
|
||||
|
||||
fn is_nan(self) -> bool;
|
||||
fn is_infinite(self) -> bool;
|
||||
fn to_bits(self) -> Self::Int;
|
||||
fn from_bits(bits: Self::Int) -> Self;
|
||||
fn signum(self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_float {
|
||||
($($fty:ty, $ui:ty, $si:ty, $significand_bits:expr;)+) => {
|
||||
$(
|
||||
impl Float for $fty {
|
||||
type Int = $ui;
|
||||
type SignedInt = $si;
|
||||
|
||||
const ZERO: Self = 0.0;
|
||||
const ONE: Self = 1.0;
|
||||
|
||||
const BITS: u32 = <$ui>::BITS;
|
||||
const SIGNIFICAND_BITS: u32 = $significand_bits;
|
||||
|
||||
fn is_nan(self) -> bool {
|
||||
self.is_nan()
|
||||
}
|
||||
fn is_infinite(self) -> bool {
|
||||
self.is_infinite()
|
||||
}
|
||||
fn to_bits(self) -> Self::Int {
|
||||
self.to_bits()
|
||||
}
|
||||
fn from_bits(bits: Self::Int) -> Self {
|
||||
Self::from_bits(bits)
|
||||
}
|
||||
fn signum(self) -> Self {
|
||||
self.signum()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hex for $fty {
|
||||
fn hex(self) -> String {
|
||||
self.to_bits().hex()
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
impl_float!(
|
||||
f32, u32, i32, 23;
|
||||
f64, u64, i64, 52;
|
||||
);
|
||||
|
||||
/// Common types and methods for integers.
|
||||
pub trait Int: Copy + fmt::Display + fmt::Debug + PartialEq<Self> {
|
||||
type OtherSign: Int;
|
||||
type Unsigned: Int;
|
||||
const BITS: u32;
|
||||
const SIGNED: bool;
|
||||
|
||||
fn signed(self) -> <Self::Unsigned as Int>::OtherSign;
|
||||
fn unsigned(self) -> Self::Unsigned;
|
||||
fn checked_sub(self, other: Self) -> Option<Self>;
|
||||
fn abs(self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_int {
|
||||
($($ui:ty, $si:ty ;)+) => {
|
||||
$(
|
||||
impl Int for $ui {
|
||||
type OtherSign = $si;
|
||||
type Unsigned = Self;
|
||||
const BITS: u32 = <$ui>::BITS;
|
||||
const SIGNED: bool = false;
|
||||
fn signed(self) -> Self::OtherSign {
|
||||
self as $si
|
||||
}
|
||||
fn unsigned(self) -> Self {
|
||||
self
|
||||
}
|
||||
fn checked_sub(self, other: Self) -> Option<Self> {
|
||||
self.checked_sub(other)
|
||||
}
|
||||
fn abs(self) -> Self {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Int for $si {
|
||||
type OtherSign = $ui;
|
||||
type Unsigned = $ui;
|
||||
const BITS: u32 = <$ui>::BITS;
|
||||
const SIGNED: bool = true;
|
||||
fn signed(self) -> Self {
|
||||
self
|
||||
}
|
||||
fn unsigned(self) -> $ui {
|
||||
self as $ui
|
||||
}
|
||||
fn checked_sub(self, other: Self) -> Option<Self> {
|
||||
self.checked_sub(other)
|
||||
}
|
||||
fn abs(self) -> Self {
|
||||
self.abs()
|
||||
}
|
||||
}
|
||||
|
||||
impl_int!(@for_both $si);
|
||||
impl_int!(@for_both $ui);
|
||||
|
||||
)+
|
||||
};
|
||||
|
||||
(@for_both $ty:ty) => {
|
||||
impl Hex for $ty {
|
||||
fn hex(self) -> String {
|
||||
format!("{self:#0width$x}", width = ((Self::BITS / 4) + 2) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Input> $crate::CheckOutput<Input> for $ty
|
||||
where
|
||||
Input: Hex + fmt::Debug,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
fn validate<'a>(
|
||||
self,
|
||||
expected: Self,
|
||||
input: Input,
|
||||
ctx: &$crate::CheckCtx,
|
||||
) -> TestResult {
|
||||
if let Some(res) = SpecialCase::check_int(input, self, expected, ctx) {
|
||||
return res;
|
||||
}
|
||||
|
||||
anyhow::ensure!(
|
||||
self == expected,
|
||||
"\
|
||||
\n input: {input:?} {ibits}\
|
||||
\n expected: {expected:<22?} {expbits}\
|
||||
\n actual: {self:<22?} {actbits}\
|
||||
",
|
||||
actbits = self.hex(),
|
||||
expbits = expected.hex(),
|
||||
ibits = input.hex(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_int!(
|
||||
u32, i32;
|
||||
u64, i64;
|
||||
);
|
||||
|
||||
/// A helper trait to print something as hex with the correct number of nibbles, e.g. a `u32`
|
||||
/// will always print with `0x` followed by 8 digits.
|
||||
///
|
||||
/// This is only used for printing errors so allocating is okay.
|
||||
pub trait Hex: Copy {
|
||||
fn hex(self) -> String;
|
||||
}
|
||||
|
||||
impl<T1> Hex for (T1,)
|
||||
where
|
||||
T1: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({},)", self.0.hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2> Hex for (T1, T2)
|
||||
where
|
||||
T1: Hex,
|
||||
T2: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({}, {})", self.0.hex(), self.1.hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3> Hex for (T1, T2, T3)
|
||||
where
|
||||
T1: Hex,
|
||||
T2: Hex,
|
||||
T3: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({}, {}, {})", self.0.hex(), self.1.hex(), self.2.hex())
|
||||
}
|
||||
}
|
||||
@@ -11,21 +11,7 @@ use std::fmt;
|
||||
|
||||
use anyhow::{Context, bail, ensure};
|
||||
|
||||
use crate::{Float, Hex, Int, MaybeOverride, SpecialCase, TestResult};
|
||||
|
||||
/// Implement this on types that can generate a sequence of tuples for test input.
|
||||
pub trait GenerateInput<TupleArgs> {
|
||||
fn get_cases(&self) -> impl Iterator<Item = TupleArgs>;
|
||||
}
|
||||
|
||||
/// Trait for calling a function with a tuple as arguments.
|
||||
///
|
||||
/// Implemented on the tuple with the function signature as the generic (so we can use the same
|
||||
/// tuple for multiple signatures).
|
||||
pub trait TupleCall<Func>: fmt::Debug {
|
||||
type Output;
|
||||
fn call(self, f: Func) -> Self::Output;
|
||||
}
|
||||
use crate::{Float, Int, MaybeOverride, SpecialCase, TestResult};
|
||||
|
||||
/// Context passed to [`CheckOutput`].
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -56,14 +42,38 @@ pub enum CheckBasis {
|
||||
Mpfr,
|
||||
}
|
||||
|
||||
/// Implement this on types that can generate a sequence of tuples for test input.
|
||||
pub trait GenerateInput<TupleArgs> {
|
||||
fn get_cases(&self) -> impl Iterator<Item = TupleArgs>;
|
||||
}
|
||||
|
||||
/// Trait for calling a function with a tuple as arguments.
|
||||
///
|
||||
/// Implemented on the tuple with the function signature as the generic (so we can use the same
|
||||
/// tuple for multiple signatures).
|
||||
pub trait TupleCall<Func>: fmt::Debug {
|
||||
type Output;
|
||||
fn call(self, f: Func) -> Self::Output;
|
||||
}
|
||||
|
||||
/// A trait to implement on any output type so we can verify it in a generic way.
|
||||
pub trait CheckOutput<Input>: Sized {
|
||||
/// Validate `self` (actual) and `expected` are the same.
|
||||
///
|
||||
/// `input` is only used here for error messages.
|
||||
fn validate<'a>(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult;
|
||||
fn validate(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult;
|
||||
}
|
||||
|
||||
/// A helper trait to print something as hex with the correct number of nibbles, e.g. a `u32`
|
||||
/// will always print with `0x` followed by 8 digits.
|
||||
///
|
||||
/// This is only used for printing errors so allocating is okay.
|
||||
pub trait Hex: Copy {
|
||||
fn hex(self) -> String;
|
||||
}
|
||||
|
||||
/* implement `TupleCall` */
|
||||
|
||||
impl<T1, R> TupleCall<fn(T1) -> R> for (T1,)
|
||||
where
|
||||
T1: fmt::Debug,
|
||||
@@ -143,72 +153,193 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// Implement for floats
|
||||
impl<F, Input> CheckOutput<Input> for F
|
||||
/* implement `Hex` */
|
||||
|
||||
impl<T1> Hex for (T1,)
|
||||
where
|
||||
T1: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({},)", self.0.hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2> Hex for (T1, T2)
|
||||
where
|
||||
T1: Hex,
|
||||
T2: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({}, {})", self.0.hex(), self.1.hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T1, T2, T3> Hex for (T1, T2, T3)
|
||||
where
|
||||
T1: Hex,
|
||||
T2: Hex,
|
||||
T3: Hex,
|
||||
{
|
||||
fn hex(self) -> String {
|
||||
format!("({}, {}, {})", self.0.hex(), self.1.hex(), self.2.hex())
|
||||
}
|
||||
}
|
||||
|
||||
/* trait implementations for ints */
|
||||
|
||||
macro_rules! impl_int {
|
||||
($($ty:ty),*) => {
|
||||
$(
|
||||
impl Hex for $ty {
|
||||
fn hex(self) -> String {
|
||||
format!("{self:#0width$x}", width = ((Self::BITS / 4) + 2) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Input> $crate::CheckOutput<Input> for $ty
|
||||
where
|
||||
Input: Hex + fmt::Debug,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
fn validate<'a>(
|
||||
self,
|
||||
expected: Self,
|
||||
input: Input,
|
||||
ctx: &$crate::CheckCtx,
|
||||
) -> TestResult {
|
||||
validate_int(self, expected, input, ctx)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_int<'a, I, Input>(actual: I, expected: I, input: Input, ctx: &CheckCtx) -> TestResult
|
||||
where
|
||||
I: Int + Hex,
|
||||
Input: Hex + fmt::Debug,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
if let Some(res) = SpecialCase::check_int(input, actual, expected, ctx) {
|
||||
return res;
|
||||
}
|
||||
|
||||
anyhow::ensure!(
|
||||
actual == expected,
|
||||
"\
|
||||
\n input: {input:?} {ibits}\
|
||||
\n expected: {expected:<22?} {expbits}\
|
||||
\n actual: {actual:<22?} {actbits}\
|
||||
",
|
||||
actbits = actual.hex(),
|
||||
expbits = expected.hex(),
|
||||
ibits = input.hex(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_int!(u32, i32, u64, i64);
|
||||
|
||||
/* trait implementations for floats */
|
||||
|
||||
macro_rules! impl_float {
|
||||
($($ty:ty),*) => {
|
||||
$(
|
||||
impl Hex for $ty {
|
||||
fn hex(self) -> String {
|
||||
format!(
|
||||
"{:#0width$x}",
|
||||
self.to_bits(),
|
||||
width = ((Self::BITS / 4) + 2) as usize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Input> $crate::CheckOutput<Input> for $ty
|
||||
where
|
||||
Input: Hex + fmt::Debug,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
fn validate<'a>(
|
||||
self,
|
||||
expected: Self,
|
||||
input: Input,
|
||||
ctx: &$crate::CheckCtx,
|
||||
) -> TestResult {
|
||||
validate_float(self, expected, input, ctx)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_float<'a, F, Input>(actual: F, expected: F, input: Input, ctx: &CheckCtx) -> TestResult
|
||||
where
|
||||
F: Float + Hex,
|
||||
Input: Hex + fmt::Debug,
|
||||
u32: TryFrom<F::SignedInt, Error: fmt::Debug>,
|
||||
SpecialCase: MaybeOverride<Input>,
|
||||
{
|
||||
fn validate<'a>(self, expected: Self, input: Input, ctx: &CheckCtx) -> TestResult {
|
||||
// Create a wrapper function so we only need to `.with_context` once.
|
||||
let inner = || -> TestResult {
|
||||
let mut allowed_ulp = ctx.ulp;
|
||||
// Create a wrapper function so we only need to `.with_context` once.
|
||||
let inner = || -> TestResult {
|
||||
let mut allowed_ulp = ctx.ulp;
|
||||
|
||||
// If the tested function requires a nonstandard test, run it here.
|
||||
if let Some(res) =
|
||||
SpecialCase::check_float(input, self, expected, &mut allowed_ulp, ctx)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
// If the tested function requires a nonstandard test, run it here.
|
||||
if let Some(res) = SpecialCase::check_float(input, actual, expected, &mut allowed_ulp, ctx)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
// Check when both are NaNs
|
||||
if self.is_nan() && expected.is_nan() {
|
||||
// By default, NaNs have nothing special to check.
|
||||
return Ok(());
|
||||
} else if self.is_nan() || expected.is_nan() {
|
||||
// Check when only one is a NaN
|
||||
bail!("real value != NaN")
|
||||
}
|
||||
// Check when both are NaNs
|
||||
if actual.is_nan() && expected.is_nan() {
|
||||
// By default, NaNs have nothing special to check.
|
||||
return Ok(());
|
||||
} else if actual.is_nan() || expected.is_nan() {
|
||||
// Check when only one is a NaN
|
||||
bail!("real value != NaN")
|
||||
}
|
||||
|
||||
// Make sure that the signs are the same before checing ULP to avoid wraparound
|
||||
let act_sig = self.signum();
|
||||
let exp_sig = expected.signum();
|
||||
ensure!(act_sig == exp_sig, "mismatched signs {act_sig} {exp_sig}");
|
||||
// Make sure that the signs are the same before checing ULP to avoid wraparound
|
||||
let act_sig = actual.signum();
|
||||
let exp_sig = expected.signum();
|
||||
ensure!(act_sig == exp_sig, "mismatched signs {act_sig} {exp_sig}");
|
||||
|
||||
if self.is_infinite() ^ expected.is_infinite() {
|
||||
bail!("mismatched infinities");
|
||||
}
|
||||
if actual.is_infinite() ^ expected.is_infinite() {
|
||||
bail!("mismatched infinities");
|
||||
}
|
||||
|
||||
let act_bits = self.to_bits().signed();
|
||||
let exp_bits = expected.to_bits().signed();
|
||||
let act_bits = actual.to_bits().signed();
|
||||
let exp_bits = expected.to_bits().signed();
|
||||
|
||||
let ulp_diff = act_bits.checked_sub(exp_bits).unwrap().abs();
|
||||
let ulp_diff = act_bits.checked_sub(exp_bits).unwrap().abs();
|
||||
|
||||
let ulp_u32 = u32::try_from(ulp_diff)
|
||||
.map_err(|e| anyhow::anyhow!("{e:?}: ulp of {ulp_diff} exceeds u32::MAX"))?;
|
||||
let ulp_u32 = u32::try_from(ulp_diff)
|
||||
.map_err(|e| anyhow::anyhow!("{e:?}: ulp of {ulp_diff} exceeds u32::MAX"))?;
|
||||
|
||||
ensure!(ulp_u32 <= allowed_ulp, "ulp {ulp_diff} > {allowed_ulp}",);
|
||||
ensure!(ulp_u32 <= allowed_ulp, "ulp {ulp_diff} > {allowed_ulp}",);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
Ok(())
|
||||
};
|
||||
|
||||
inner().with_context(|| {
|
||||
format!(
|
||||
"\
|
||||
\n input: {input:?} {ibits}\
|
||||
\n expected: {expected:<22?} {expbits}\
|
||||
\n actual: {self:<22?} {actbits}\
|
||||
",
|
||||
actbits = self.hex(),
|
||||
expbits = expected.hex(),
|
||||
ibits = input.hex(),
|
||||
)
|
||||
})
|
||||
}
|
||||
inner().with_context(|| {
|
||||
format!(
|
||||
"\
|
||||
\n input: {input:?} {ibits}\
|
||||
\n expected: {expected:<22?} {expbits}\
|
||||
\n actual: {actual:<22?} {actbits}\
|
||||
",
|
||||
actbits = actual.hex(),
|
||||
expbits = expected.hex(),
|
||||
ibits = input.hex(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
impl_float!(f32, f64);
|
||||
|
||||
/* trait implementations for compound types */
|
||||
|
||||
/// Implement `CheckOutput` for combinations of types.
|
||||
macro_rules! impl_tuples {
|
||||
($(($a:ty, $b:ty);)*) => {
|
||||
|
||||
Reference in New Issue
Block a user