Rework tests to make use of the new MathOp trait
This commit is contained in:
parent
7db74d78e8
commit
f7f24a4ed8
5 changed files with 272 additions and 309 deletions
|
|
@ -2,74 +2,105 @@ use std::hint::black_box;
|
|||
use std::time::Duration;
|
||||
|
||||
use criterion::{Criterion, criterion_main};
|
||||
use libm_test::gen::random;
|
||||
use libm_test::{CheckBasis, CheckCtx, TupleCall};
|
||||
use libm_test::gen::{CachedInput, random};
|
||||
use libm_test::{CheckBasis, CheckCtx, GenerateInput, MathOp, TupleCall};
|
||||
|
||||
/// Benchmark with this many items to get a variety
|
||||
const BENCH_ITER_ITEMS: usize = if cfg!(feature = "short-benchmarks") { 50 } else { 500 };
|
||||
|
||||
/// Extra parameters we only care about if we are benchmarking against musl.
|
||||
#[allow(dead_code)]
|
||||
struct MuslExtra<F> {
|
||||
musl_fn: Option<F>,
|
||||
skip_on_i586: bool,
|
||||
}
|
||||
|
||||
macro_rules! musl_rand_benches {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
fn_extra: $skip_on_i586:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
fn [< musl_bench_ $fn_name >](c: &mut Criterion) {
|
||||
let fn_name = stringify!($fn_name);
|
||||
type Op = libm_test::op::$fn_name::Routine;
|
||||
|
||||
let ulp = libm_test::musl_allowed_ulp(fn_name);
|
||||
let ctx = CheckCtx::new(ulp, fn_name, CheckBasis::Musl);
|
||||
let benchvec: Vec<_> = random::get_test_cases::<$RustArgs>(&ctx)
|
||||
.take(BENCH_ITER_ITEMS)
|
||||
.collect();
|
||||
|
||||
// Perform a sanity check that we are benchmarking the same thing
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
for input in benchvec.iter().copied() {
|
||||
use anyhow::Context;
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput};
|
||||
let musl_extra = MuslExtra {
|
||||
musl_fn: Some(musl_math_sys::$fn_name as <Op as MathOp>::CFn),
|
||||
skip_on_i586: $skip_on_i586
|
||||
};
|
||||
|
||||
if cfg!(x86_no_sse) && $skip_on_i586 {
|
||||
break;
|
||||
}
|
||||
#[cfg(not(feature = "build-musl"))]
|
||||
let musl_extra = MuslExtra {
|
||||
musl_fn: None,
|
||||
skip_on_i586: $skip_on_i586
|
||||
};
|
||||
|
||||
let musl_res = input.call(musl_math_sys::$fn_name as $CFn);
|
||||
let crate_res = input.call(libm::$fn_name as $RustFn);
|
||||
|
||||
let ctx = CheckCtx::new(ulp, fn_name, CheckBasis::Musl);
|
||||
crate_res.validate(musl_res, input, &ctx).context(fn_name).unwrap();
|
||||
}
|
||||
|
||||
/* Function pointers are black boxed to avoid inlining in the benchmark loop */
|
||||
|
||||
let mut group = c.benchmark_group(fn_name);
|
||||
group.bench_function("crate", |b| b.iter(|| {
|
||||
let f = black_box(libm::$fn_name as $RustFn);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
}));
|
||||
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
group.bench_function("musl", |b| b.iter(|| {
|
||||
let f = black_box(musl_math_sys::$fn_name as $CFn);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
}));
|
||||
bench_one::<Op>(c, musl_extra);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn bench_one<Op>(c: &mut Criterion, musl_extra: MuslExtra<Op::CFn>)
|
||||
where
|
||||
Op: MathOp,
|
||||
CachedInput: GenerateInput<Op::RustArgs>,
|
||||
{
|
||||
let name = Op::NAME_STR;
|
||||
|
||||
let ulp = libm_test::musl_allowed_ulp(name);
|
||||
let ctx = CheckCtx::new(ulp, name, CheckBasis::Musl);
|
||||
let benchvec: Vec<_> =
|
||||
random::get_test_cases::<Op::RustArgs>(&ctx).take(BENCH_ITER_ITEMS).collect();
|
||||
|
||||
// Perform a sanity check that we are benchmarking the same thing
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
for input in benchvec.iter().copied() {
|
||||
use anyhow::Context;
|
||||
use libm_test::CheckOutput;
|
||||
|
||||
if cfg!(x86_no_sse) && musl_extra.skip_on_i586 {
|
||||
break;
|
||||
}
|
||||
|
||||
let musl_res = input.call(musl_extra.musl_fn.unwrap());
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).context(name).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "build-musl"))]
|
||||
let _ = musl_extra; // silence unused warnings
|
||||
|
||||
/* Option pointers are black boxed to avoid inlining in the benchmark loop */
|
||||
|
||||
let mut group = c.benchmark_group(name);
|
||||
group.bench_function("crate", |b| {
|
||||
b.iter(|| {
|
||||
let f = black_box(Op::ROUTINE);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Don't test against musl if it is not available
|
||||
#[cfg(feature = "build-musl")]
|
||||
{
|
||||
let musl_fn = musl_extra.musl_fn.unwrap();
|
||||
group.bench_function("musl", |b| {
|
||||
b.iter(|| {
|
||||
let f = black_box(musl_fn);
|
||||
for input in benchvec.iter().copied() {
|
||||
input.call(f);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: musl_rand_benches,
|
||||
skip: [],
|
||||
|
|
@ -83,12 +114,6 @@ libm_macros::for_each_function! {
|
|||
macro_rules! run_callback {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $_CFn:ty,
|
||||
CArgs: $_CArgs:ty,
|
||||
CRet: $_CRet:ty,
|
||||
RustFn: $_RustFn:ty,
|
||||
RustArgs: $_RustArgs:ty,
|
||||
RustRet: $_RustRet:ty,
|
||||
extra: [$criterion:ident],
|
||||
) => {
|
||||
paste::paste! {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub use rug::Float as MpFloat;
|
|||
use rug::float::Round::Nearest;
|
||||
use rug::ops::{PowAssignRound, RemAssignRound};
|
||||
|
||||
use crate::Float;
|
||||
use crate::{Float, MathOp};
|
||||
|
||||
/// Create a multiple-precision float with the correct number of bits for a concrete float type.
|
||||
fn new_mpfloat<F: Float>() -> MpFloat {
|
||||
|
|
@ -29,23 +29,19 @@ where
|
|||
|
||||
/// Structures that represent a float operation.
|
||||
///
|
||||
/// The struct itself should hold any context that can be reused among calls to `run` (allocated
|
||||
/// `MpFloat`s).
|
||||
pub trait MpOp {
|
||||
/// Inputs to the operation (concrete float types).
|
||||
type Input;
|
||||
|
||||
/// Outputs from the operation (concrete float types).
|
||||
type Output;
|
||||
pub trait MpOp: MathOp {
|
||||
/// The struct itself should hold any context that can be reused among calls to `run` (allocated
|
||||
/// `MpFloat`s).
|
||||
type MpTy;
|
||||
|
||||
/// Create a new instance.
|
||||
fn new() -> Self;
|
||||
fn new_mp() -> Self::MpTy;
|
||||
|
||||
/// Perform the operation.
|
||||
///
|
||||
/// Usually this means assigning inputs to cached floats, performing the operation, applying
|
||||
/// subnormal approximation, and converting the result back to concrete values.
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output;
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet;
|
||||
}
|
||||
|
||||
/// Implement `MpOp` for functions with a single return value.
|
||||
|
|
@ -53,32 +49,21 @@ macro_rules! impl_mp_op {
|
|||
// Matcher for unary functions
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: fn($fty:ty,) -> $_ret:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
RustFn: fn($_fty:ty,) -> $_ret:ty,
|
||||
fn_extra: $fn_name_normalized:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat);
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = $RustArgs;
|
||||
type Output = $RustRet;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
let ord = self.0.[< $fn_name_normalized _round >](Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
let ord = this.[< $fn_name_normalized _round >](Nearest);
|
||||
prep_retval::<Self::RustRet>(this, ord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,33 +71,22 @@ macro_rules! impl_mp_op {
|
|||
// Matcher for binary functions
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: fn($fty:ty, $_fty2:ty,) -> $_ret:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
RustFn: fn($_fty:ty, $_fty2:ty,) -> $_ret:ty,
|
||||
fn_extra: $fn_name_normalized:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = $RustArgs;
|
||||
type Output = $RustRet;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
let ord = self.0.[< $fn_name_normalized _round >](&self.1, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
let ord = this.0.[< $fn_name_normalized _round >](&this.1, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,34 +94,27 @@ macro_rules! impl_mp_op {
|
|||
// Matcher for ternary functions
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: fn($fty:ty, $_fty2:ty, $_fty3:ty,) -> $_ret:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
RustFn: fn($_fty:ty, $_fty2:ty, $_fty3:ty,) -> $_ret:ty,
|
||||
fn_extra: $fn_name_normalized:expr,
|
||||
) => {
|
||||
paste::paste! {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = (MpFloat, MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = $RustArgs;
|
||||
type Output = $RustRet;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(
|
||||
new_mpfloat::<Self::FTy>(),
|
||||
new_mpfloat::<Self::FTy>(),
|
||||
new_mpfloat::<Self::FTy>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
self.2.assign(input.2);
|
||||
let ord = self.0.[< $fn_name_normalized _round >](&self.1, &self.2, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
this.2.assign(input.2);
|
||||
let ord = this.0.[< $fn_name_normalized _round >](&this.1, &this.2, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,6 +123,7 @@ macro_rules! impl_mp_op {
|
|||
|
||||
libm_macros::for_each_function! {
|
||||
callback: impl_mp_op,
|
||||
emit_types: [RustFn],
|
||||
skip: [
|
||||
// Most of these need a manual implementation
|
||||
fabs, ceil, copysign, floor, rint, round, trunc,
|
||||
|
|
@ -186,29 +154,23 @@ macro_rules! impl_no_round {
|
|||
($($fn_name:ident, $rug_name:ident;)*) => {
|
||||
paste::paste! {
|
||||
// Implement for both f32 and f64
|
||||
$( impl_no_round!{ @inner_unary [< $fn_name f >], (f32,), $rug_name } )*
|
||||
$( impl_no_round!{ @inner_unary $fn_name, (f64,), $rug_name } )*
|
||||
$( impl_no_round!{ @inner_unary [< $fn_name f >], $rug_name } )*
|
||||
$( impl_no_round!{ @inner_unary $fn_name, $rug_name } )*
|
||||
}
|
||||
};
|
||||
|
||||
(@inner_unary $fn_name:ident, ($fty:ty,), $rug_name:ident) => {
|
||||
pub mod $fn_name {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat);
|
||||
(@inner_unary $fn_name:ident, $rug_name:ident) => {
|
||||
impl MpOp for crate::op::$fn_name::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty,);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.0.$rug_name();
|
||||
prep_retval::<Self::Output>(&mut self.0, Ordering::Equal)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
this.$rug_name();
|
||||
prep_retval::<Self::RustRet>(this, Ordering::Equal)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -227,132 +189,81 @@ impl_no_round! {
|
|||
macro_rules! impl_op_for_ty {
|
||||
($fty:ty, $suffix:literal) => {
|
||||
paste::paste! {
|
||||
pub mod [<copysign $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::[<copysign $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty, $fty);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
self.0.copysign_mut(&self.1);
|
||||
prep_retval::<Self::Output>(&mut self.0, Ordering::Equal)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
this.0.copysign_mut(&this.1);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, Ordering::Equal)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<pow $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::[<pow $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty, $fty);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
let ord = self.0.pow_assign_round(&self.1, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
let ord = this.0.pow_assign_round(&this.1, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<fmod $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
impl MpOp for crate::op::[<fmod $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty, $fty);
|
||||
type Output = $fty;
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(input.1);
|
||||
let ord = self.0.rem_assign_round(&self.1, Nearest);
|
||||
prep_retval::<Self::Output>(&mut self.0, ord)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(input.1);
|
||||
let ord = this.0.rem_assign_round(&this.1, Nearest);
|
||||
prep_retval::<Self::RustRet>(&mut this.0, ord)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<lgamma_r $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat);
|
||||
impl MpOp for crate::op::[<jn $suffix>]::Routine {
|
||||
type MpTy = (i32, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty,);
|
||||
type Output = ($fty, i32);
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(0, new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
let (sign, ord) = self.0.ln_abs_gamma_round(Nearest);
|
||||
let ret = prep_retval::<$fty>(&mut self.0, ord);
|
||||
(ret, sign as i32)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0 = input.0;
|
||||
this.1.assign(input.1);
|
||||
let ord = this.1.jn_round(this.0, Nearest);
|
||||
prep_retval::<Self::FTy>(&mut this.1, ord)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<jn $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(i32, MpFloat);
|
||||
impl MpOp for crate::op::[<sincos $suffix>]::Routine {
|
||||
type MpTy = (MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = (i32, $fty);
|
||||
type Output = $fty;
|
||||
|
||||
fn new() -> Self {
|
||||
Self(0, new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0 = input.0;
|
||||
self.1.assign(input.1);
|
||||
let ord = self.1.jn_round(self.0, Nearest);
|
||||
prep_retval::<$fty>(&mut self.1, ord)
|
||||
}
|
||||
fn new_mp() -> Self::MpTy {
|
||||
(new_mpfloat::<Self::FTy>(), new_mpfloat::<Self::FTy>())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod [<sincos $suffix>] {
|
||||
use super::*;
|
||||
pub struct Operation(MpFloat, MpFloat);
|
||||
|
||||
impl MpOp for Operation {
|
||||
type Input = ($fty,);
|
||||
type Output = ($fty, $fty);
|
||||
|
||||
fn new() -> Self {
|
||||
Self(new_mpfloat::<$fty>(), new_mpfloat::<$fty>())
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::Input) -> Self::Output {
|
||||
self.0.assign(input.0);
|
||||
self.1.assign(0.0);
|
||||
let (sord, cord) = self.0.sin_cos_round(&mut self.1, Nearest);
|
||||
(
|
||||
prep_retval::<$fty>(&mut self.0, sord),
|
||||
prep_retval::<$fty>(&mut self.1, cord)
|
||||
)
|
||||
}
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.0.assign(input.0);
|
||||
this.1.assign(0.0);
|
||||
let (sord, cord) = this.0.sin_cos_round(&mut this.1, Nearest);
|
||||
(
|
||||
prep_retval::<Self::FTy>(&mut this.0, sord),
|
||||
prep_retval::<Self::FTy>(&mut this.1, cord)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +273,33 @@ macro_rules! impl_op_for_ty {
|
|||
impl_op_for_ty!(f32, "f");
|
||||
impl_op_for_ty!(f64, "");
|
||||
|
||||
// Account for `lgamma_r` not having a simple `f` suffix
|
||||
pub mod lgammaf_r {
|
||||
pub use super::lgamma_rf::*;
|
||||
// `lgamma_r` is not a simple suffix so we can't use the above macro.
|
||||
impl MpOp for crate::op::lgamma_r::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
let (sign, ord) = this.ln_abs_gamma_round(Nearest);
|
||||
let ret = prep_retval::<Self::FTy>(this, ord);
|
||||
(ret, sign as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl MpOp for crate::op::lgammaf_r::Routine {
|
||||
type MpTy = MpFloat;
|
||||
|
||||
fn new_mp() -> Self::MpTy {
|
||||
new_mpfloat::<Self::FTy>()
|
||||
}
|
||||
|
||||
fn run(this: &mut Self::MpTy, input: Self::RustArgs) -> Self::RustRet {
|
||||
this.assign(input.0);
|
||||
let (sign, ord) = this.ln_abs_gamma_round(Nearest);
|
||||
let ret = prep_retval::<Self::FTy>(this, ord);
|
||||
(ret, sign as i32)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,6 @@ const ALLOWED_SKIPS: &[&str] = &[
|
|||
macro_rules! callback {
|
||||
(
|
||||
fn_name: $name:ident,
|
||||
CFn: $_CFn:ty,
|
||||
CArgs: $_CArgs:ty,
|
||||
CRet: $_CRet:ty,
|
||||
RustFn: $_RustFn:ty,
|
||||
RustArgs: $_RustArgs:ty,
|
||||
RustRet: $_RustRet:ty,
|
||||
extra: [$push_to:ident],
|
||||
) => {
|
||||
$push_to.push(stringify!($name));
|
||||
|
|
|
|||
|
|
@ -9,42 +9,46 @@
|
|||
// There are some targets we can't build musl for
|
||||
#![cfg(feature = "build-musl")]
|
||||
|
||||
use libm_test::gen::random;
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, TupleCall, musl_allowed_ulp};
|
||||
use musl_math_sys as musl;
|
||||
use libm_test::gen::{CachedInput, random};
|
||||
use libm_test::{
|
||||
CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, TupleCall, musl_allowed_ulp,
|
||||
};
|
||||
|
||||
macro_rules! musl_rand_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
attrs: [$($meta:meta)*]
|
||||
) => { paste::paste! {
|
||||
#[test]
|
||||
$(#[$meta])*
|
||||
fn [< musl_random_ $fn_name >]() {
|
||||
let fname = stringify!($fn_name);
|
||||
let ulp = musl_allowed_ulp(fname);
|
||||
let ctx = CheckCtx::new(ulp, fname, CheckBasis::Musl);
|
||||
let cases = random::get_test_cases::<$RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl::$fn_name as $CFn);
|
||||
let crate_res = input.call(libm::$fn_name as $RustFn);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).unwrap();
|
||||
) => {
|
||||
paste::paste! {
|
||||
#[test]
|
||||
$(#[$meta])*
|
||||
fn [< musl_random_ $fn_name >]() {
|
||||
test_one::<libm_test::op::$fn_name::Routine>(musl_math_sys::$fn_name);
|
||||
}
|
||||
}
|
||||
} };
|
||||
};
|
||||
}
|
||||
|
||||
fn test_one<Op>(musl_fn: Op::CFn)
|
||||
where
|
||||
Op: MathOp,
|
||||
CachedInput: GenerateInput<Op::RustArgs>,
|
||||
{
|
||||
let name = Op::NAME_STR;
|
||||
let ulp = musl_allowed_ulp(name);
|
||||
let ctx = CheckCtx::new(ulp, name, CheckBasis::Musl);
|
||||
let cases = random::get_test_cases::<Op::RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let musl_res = input.call(musl_fn);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(musl_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: musl_rand_tests,
|
||||
skip: [],
|
||||
attributes: [
|
||||
#[cfg_attr(x86_no_sse, ignore)] // FIXME(correctness): wrong result on i586
|
||||
[exp10, exp10f, exp2, exp2f, rint]
|
||||
|
|
|
|||
|
|
@ -2,45 +2,48 @@
|
|||
|
||||
#![cfg(feature = "test-multiprecision")]
|
||||
|
||||
use libm_test::gen::random;
|
||||
use libm_test::mpfloat::{self, MpOp};
|
||||
use libm_test::{CheckBasis, CheckCtx, CheckOutput, TupleCall, multiprec_allowed_ulp};
|
||||
use libm_test::gen::{CachedInput, random};
|
||||
use libm_test::mpfloat::MpOp;
|
||||
use libm_test::{
|
||||
CheckBasis, CheckCtx, CheckOutput, GenerateInput, MathOp, TupleCall, multiprec_allowed_ulp,
|
||||
};
|
||||
|
||||
/// Implement a test against MPFR with random inputs.
|
||||
macro_rules! multiprec_rand_tests {
|
||||
(
|
||||
fn_name: $fn_name:ident,
|
||||
CFn: $CFn:ty,
|
||||
CArgs: $CArgs:ty,
|
||||
CRet: $CRet:ty,
|
||||
RustFn: $RustFn:ty,
|
||||
RustArgs: $RustArgs:ty,
|
||||
RustRet: $RustRet:ty,
|
||||
attrs: [$($meta:meta)*]
|
||||
) => {
|
||||
paste::paste! {
|
||||
#[test]
|
||||
$(#[$meta])*
|
||||
fn [< multiprec_random_ $fn_name >]() {
|
||||
type MpOpTy = mpfloat::$fn_name::Operation;
|
||||
|
||||
let fname = stringify!($fn_name);
|
||||
let ulp = multiprec_allowed_ulp(fname);
|
||||
let mut mp_vals = MpOpTy::new();
|
||||
let ctx = CheckCtx::new(ulp, fname, CheckBasis::Mpfr);
|
||||
let cases = random::get_test_cases::<$RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let mp_res = mp_vals.run(input);
|
||||
let crate_res = input.call(libm::$fn_name as $RustFn);
|
||||
|
||||
crate_res.validate(mp_res, input, &ctx).unwrap();
|
||||
}
|
||||
test_one::<libm_test::op::$fn_name::Routine>();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn test_one<Op>()
|
||||
where
|
||||
Op: MathOp + MpOp,
|
||||
CachedInput: GenerateInput<Op::RustArgs>,
|
||||
{
|
||||
let name = Op::NAME_STR;
|
||||
|
||||
let ulp = multiprec_allowed_ulp(name);
|
||||
let mut mp_vals = Op::new_mp();
|
||||
let ctx = CheckCtx::new(ulp, name, CheckBasis::Mpfr);
|
||||
let cases = random::get_test_cases::<Op::RustArgs>(&ctx);
|
||||
|
||||
for input in cases {
|
||||
let mp_res = Op::run(&mut mp_vals, input);
|
||||
let crate_res = input.call(Op::ROUTINE);
|
||||
|
||||
crate_res.validate(mp_res, input, &ctx).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
libm_macros::for_each_function! {
|
||||
callback: multiprec_rand_tests,
|
||||
attributes: [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue