Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit 22dd315

Browse files
committed
Add a utility crate for quick evaluation
Introduce a simple binary that can run arbitrary input against any of the available implementations (musl, MPFR, our libm). This provides an easy way to check results, or run specific cases against a debugger. Examples: $ cargo run -p util -- eval libm pow 1.6 2.4 3.089498284311124 $ cargo run -p util -- eval mpfr pow 1.6 2.4 3.089498284311124 $ cargo run -p util -- eval musl tgamma 1.2344597839132 0.9097442657960874 $ cargo run -p util -- eval mpfr tgamma 1.2344597839132 0.9097442657960874 $ cargo run -p util -- eval libm tgamma 1.2344597839132 0.9097442657960871 $ cargo run -p util -- eval musl sincos 3.1415926535 (8.979318433952318e-11, -1.0)
1 parent 36ed2a5 commit 22dd315

File tree

5 files changed

+298
-0
lines changed

5 files changed

+298
-0
lines changed

libm/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ members = [
4646
"crates/libm-macros",
4747
"crates/libm-test",
4848
"crates/musl-math-sys",
49+
"crates/util",
4950
]
5051
default-members = [
5152
".",

libm/crates/libm-test/src/mpfloat.rs

+26
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,32 @@ impl MpOp for crate::op::lgammaf_r::Routine {
471471
}
472472
}
473473

474+
/* stub implementations so we don't need to special case them */
475+
476+
impl MpOp for crate::op::nextafter::Routine {
477+
type MpTy = MpFloat;
478+
479+
fn new_mp() -> Self::MpTy {
480+
unimplemented!("nextafter does not yet have a MPFR operation");
481+
}
482+
483+
fn run(_this: &mut Self::MpTy, _input: Self::RustArgs) -> Self::RustRet {
484+
unimplemented!("nextafter does not yet have a MPFR operation");
485+
}
486+
}
487+
488+
impl MpOp for crate::op::nextafterf::Routine {
489+
type MpTy = MpFloat;
490+
491+
fn new_mp() -> Self::MpTy {
492+
unimplemented!("nextafter does not yet have a MPFR operation");
493+
}
494+
495+
fn run(_this: &mut Self::MpTy, _input: Self::RustArgs) -> Self::RustRet {
496+
unimplemented!("nextafter does not yet have a MPFR operation");
497+
}
498+
}
499+
474500
/// `rug` does not provide `remquo` so this exposes `mpfr_remquo`. See rug#76.
475501
fn mpfr_remquo(r: &mut MpFloat, x: &MpFloat, y: &MpFloat, round: Round) -> (Ordering, c_long) {
476502
let r = r.as_raw_mut();

libm/crates/util/Cargo.toml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "util"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[features]
8+
default = ["build-musl", "build-mpfr", "unstable-float"]
9+
build-musl = ["libm-test/build-musl", "dep:musl-math-sys"]
10+
build-mpfr = ["libm-test/build-mpfr", "dep:az", "dep:rug"]
11+
unstable-float = ["libm/unstable-float", "libm-test/unstable-float", "rug?/nightly-float"]
12+
13+
[dependencies]
14+
az = { version = "1.2.1", optional = true }
15+
libm = { path = "../..", default-features = false }
16+
libm-macros = { path = "../libm-macros" }
17+
libm-test = { path = "../libm-test", default-features = false }
18+
musl-math-sys = { path = "../musl-math-sys", optional = true }
19+
rug = { version = "1.26.1", optional = true, default-features = false, features = ["float", "std"] }

libm/crates/util/build.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![allow(unexpected_cfgs)]
2+
3+
#[path = "../../configure.rs"]
4+
mod configure;
5+
6+
fn main() {
7+
let cfg = configure::Config::from_env();
8+
configure::emit_libm_config(&cfg);
9+
}

libm/crates/util/src/main.rs

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
//! Helper CLI utility for common tasks.
2+
3+
#![cfg_attr(f16_enabled, feature(f16))]
4+
#![cfg_attr(f128_enabled, feature(f128))]
5+
6+
use std::any::type_name;
7+
use std::env;
8+
use std::str::FromStr;
9+
10+
#[cfg(feature = "build-mpfr")]
11+
use az::Az;
12+
#[cfg(feature = "build-mpfr")]
13+
use libm_test::mpfloat::MpOp;
14+
use libm_test::{MathOp, TupleCall};
15+
16+
const USAGE: &str = "\
17+
usage:
18+
19+
cargo run -p util -- <SUBCOMMAND>
20+
21+
SUBCOMMAND:
22+
eval <BASIS> <OP> inputs...
23+
Evaulate the expression with a given basis. This can be useful for
24+
running routines with a debugger, or quickly checking input. Examples:
25+
* eval musl sinf 1.234 # print the results of musl sinf(1.234f32)
26+
* eval mpfr pow 1.234 2.432 # print the results of mpfr pow(1.234, 2.432)
27+
";
28+
29+
fn main() {
30+
let args = env::args().collect::<Vec<_>>();
31+
let str_args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
32+
33+
match &str_args.as_slice()[1..] {
34+
["eval", basis, op, inputs @ ..] => do_eval(basis, op, inputs),
35+
_ => {
36+
println!("{USAGE}\nunrecognized input `{str_args:?}`");
37+
std::process::exit(1);
38+
}
39+
}
40+
}
41+
42+
macro_rules! handle_call {
43+
(
44+
fn_name: $fn_name:ident,
45+
CFn: $CFn:ty,
46+
RustFn: $RustFn:ty,
47+
RustArgs: $RustArgs:ty,
48+
attrs: [$($attr:meta),*],
49+
extra: ($basis:ident, $op:ident, $inputs:ident),
50+
fn_extra: $musl_fn:expr,
51+
) => {
52+
$(#[$attr])*
53+
if $op == stringify!($fn_name) {
54+
type Op = libm_test::op::$fn_name::Routine;
55+
56+
let input = <$RustArgs>::parse($inputs);
57+
let libm_fn: <Op as MathOp>::RustFn = libm::$fn_name;
58+
59+
let output = match $basis {
60+
"libm" => input.call(libm_fn),
61+
#[cfg(feature = "build-musl")]
62+
"musl" => {
63+
let musl_fn: <Op as MathOp>::CFn =
64+
$musl_fn.unwrap_or_else(|| panic!("no musl function for {}", $op));
65+
input.call(musl_fn)
66+
}
67+
#[cfg(feature = "build-mpfr")]
68+
"mpfr" => {
69+
let mut mp = <Op as MpOp>::new_mp();
70+
Op::run(&mut mp, input)
71+
}
72+
_ => panic!("unrecognized or disabled basis '{}'", $basis),
73+
};
74+
println!("{output:?}");
75+
return;
76+
}
77+
};
78+
}
79+
80+
/// Evaluate the specified operation with a given basis.
81+
fn do_eval(basis: &str, op: &str, inputs: &[&str]) {
82+
libm_macros::for_each_function! {
83+
callback: handle_call,
84+
emit_types: [CFn, RustFn, RustArgs],
85+
extra: (basis, op, inputs),
86+
fn_extra: match MACRO_FN_NAME {
87+
copysignf16 | copysignf128 | fabsf16 | fabsf128 => None,
88+
_ => Some(musl_math_sys::MACRO_FN_NAME)
89+
}
90+
}
91+
92+
panic!("no operation matching {op}");
93+
}
94+
95+
/// Parse a tuple from a space-delimited string.
96+
trait ParseTuple {
97+
fn parse(input: &[&str]) -> Self;
98+
}
99+
100+
macro_rules! impl_parse_tuple {
101+
($ty:ty) => {
102+
impl ParseTuple for ($ty,) {
103+
fn parse(input: &[&str]) -> Self {
104+
assert_eq!(input.len(), 1, "expected a single argument, got {input:?}");
105+
(parse(input, 0),)
106+
}
107+
}
108+
109+
impl ParseTuple for ($ty, $ty) {
110+
fn parse(input: &[&str]) -> Self {
111+
assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
112+
(parse(input, 0), parse(input, 1))
113+
}
114+
}
115+
116+
impl ParseTuple for ($ty, i32) {
117+
fn parse(input: &[&str]) -> Self {
118+
assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
119+
(parse(input, 0), parse(input, 1))
120+
}
121+
}
122+
123+
impl ParseTuple for (i32, $ty) {
124+
fn parse(input: &[&str]) -> Self {
125+
assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
126+
(parse(input, 0), parse(input, 1))
127+
}
128+
}
129+
130+
impl ParseTuple for ($ty, $ty, $ty) {
131+
fn parse(input: &[&str]) -> Self {
132+
assert_eq!(input.len(), 2, "expected three arguments, got {input:?}");
133+
(parse(input, 0), parse(input, 1), parse(input, 3))
134+
}
135+
}
136+
};
137+
}
138+
139+
#[allow(unused_macros)]
140+
#[cfg(feature = "build-mpfr")]
141+
macro_rules! impl_parse_tuple_via_rug {
142+
($ty:ty) => {
143+
impl ParseTuple for ($ty,) {
144+
fn parse(input: &[&str]) -> Self {
145+
assert_eq!(input.len(), 1, "expected a single argument, got {input:?}");
146+
(parse_rug(input, 0),)
147+
}
148+
}
149+
150+
impl ParseTuple for ($ty, $ty) {
151+
fn parse(input: &[&str]) -> Self {
152+
assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
153+
(parse_rug(input, 0), parse_rug(input, 1))
154+
}
155+
}
156+
157+
impl ParseTuple for ($ty, i32) {
158+
fn parse(input: &[&str]) -> Self {
159+
assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
160+
(parse_rug(input, 0), parse(input, 1))
161+
}
162+
}
163+
164+
impl ParseTuple for (i32, $ty) {
165+
fn parse(input: &[&str]) -> Self {
166+
assert_eq!(input.len(), 2, "expected two arguments, got {input:?}");
167+
(parse(input, 0), parse_rug(input, 1))
168+
}
169+
}
170+
171+
impl ParseTuple for ($ty, $ty, $ty) {
172+
fn parse(input: &[&str]) -> Self {
173+
assert_eq!(input.len(), 2, "expected three arguments, got {input:?}");
174+
(parse_rug(input, 0), parse_rug(input, 1), parse_rug(input, 3))
175+
}
176+
}
177+
};
178+
}
179+
180+
// Fallback for when Rug is not built.
181+
#[allow(unused_macros)]
182+
#[cfg(not(feature = "build-mpfr"))]
183+
macro_rules! impl_parse_tuple_via_rug {
184+
($ty:ty) => {
185+
impl ParseTuple for ($ty,) {
186+
fn parse(_input: &[&str]) -> Self {
187+
panic!("parsing this type requires the `build-mpfr` feature")
188+
}
189+
}
190+
191+
impl ParseTuple for ($ty, $ty) {
192+
fn parse(_input: &[&str]) -> Self {
193+
panic!("parsing this type requires the `build-mpfr` feature")
194+
}
195+
}
196+
197+
impl ParseTuple for ($ty, i32) {
198+
fn parse(_input: &[&str]) -> Self {
199+
panic!("parsing this type requires the `build-mpfr` feature")
200+
}
201+
}
202+
203+
impl ParseTuple for (i32, $ty) {
204+
fn parse(_input: &[&str]) -> Self {
205+
panic!("parsing this type requires the `build-mpfr` feature")
206+
}
207+
}
208+
209+
impl ParseTuple for ($ty, $ty, $ty) {
210+
fn parse(_input: &[&str]) -> Self {
211+
panic!("parsing this type requires the `build-mpfr` feature")
212+
}
213+
}
214+
};
215+
}
216+
217+
impl_parse_tuple!(f32);
218+
impl_parse_tuple!(f64);
219+
220+
#[cfg(f16_enabled)]
221+
impl_parse_tuple_via_rug!(f16);
222+
#[cfg(f128_enabled)]
223+
impl_parse_tuple_via_rug!(f128);
224+
225+
/// Try to parse the number, printing a nice message on failure.
226+
fn parse<F: FromStr>(input: &[&str], idx: usize) -> F {
227+
let s = input[idx];
228+
s.parse().unwrap_or_else(|_| panic!("invalid {} input '{s}'", type_name::<F>()))
229+
}
230+
231+
/// Try to parse the float type going via `rug`, for `f16` and `f128` which don't yet implement
232+
/// `FromStr`.
233+
#[cfg(feature = "build-mpfr")]
234+
fn parse_rug<F: libm_test::Float>(input: &[&str], idx: usize) -> F
235+
where
236+
rug::Float: az::Cast<F>,
237+
{
238+
let s = input[idx];
239+
let x =
240+
rug::Float::parse(s).unwrap_or_else(|_| panic!("invalid {} input '{s}'", type_name::<F>()));
241+
let x = rug::Float::with_val(F::BITS, x);
242+
x.az()
243+
}

0 commit comments

Comments
 (0)