Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl arbitrary::Arbitrary for NotNan and OrderedFloat. #104

Merged
merged 1 commit into from Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -19,6 +19,7 @@ serde = { version = "1.0", optional = true, default-features = false }
rkyv = { version = "0.7", optional = true, default-features = false, features = ["size_32"] }
schemars = { version = "0.6.5", optional = true }
rand = { version = "0.8.3", optional = true, default-features = false }
arbitrary = { version = "1.0.0", optional = true }
proptest = { version = "1.0.0", optional = true }

[dev-dependencies]
Expand Down
62 changes: 62 additions & 0 deletions src/lib.rs
Expand Up @@ -1977,3 +1977,65 @@ mod impl_proptest {
}
impl_arbitrary! { f32, f64 }
}

#[cfg(feature = "arbitrary")]
mod impl_arbitrary {
use super::{FloatIsNan, NotNan, OrderedFloat};
use arbitrary::{Arbitrary, Unstructured};
use num_traits::FromPrimitive;

macro_rules! impl_arbitrary {
($($f:ident),+) => {
$(
impl<'a> Arbitrary<'a> for NotNan<$f> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let float: $f = u.arbitrary()?;
match NotNan::new(float) {
Ok(notnan_value) => Ok(notnan_value),
Err(FloatIsNan) => {
// If our arbitrary float input was a NaN (encoded by exponent = max
// value), then replace it with a finite float, reusing the mantissa
// bits.
//
// This means the output is not uniformly distributed among all
// possible float values, but Arbitrary makes no promise that that
// is true.
//
// An alternative implementation would be to return an
// `arbitrary::Error`, but that is not as useful since it forces the
// caller to retry with new random/fuzzed data; and the precendent of
// `arbitrary`'s built-in implementations is to prefer the approach of
// mangling the input bits to fit.

let (mantissa, _exponent, sign) =
num_traits::Float::integer_decode(float);
let revised_float = <$f>::from_i64(
i64::from(sign) * mantissa as i64
).unwrap();

// If this unwrap() fails, then there is a bug in the above code.
Ok(NotNan::new(revised_float).unwrap())
}
}
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$f as Arbitrary>::size_hint(depth)
}
}

impl<'a> Arbitrary<'a> for OrderedFloat<$f> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let float: $f = u.arbitrary()?;
Ok(OrderedFloat::from(float))
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$f as Arbitrary>::size_hint(depth)
}
}
)*
}
}
impl_arbitrary! { f32, f64 }
}
43 changes: 43 additions & 0 deletions tests/test.rs
Expand Up @@ -692,3 +692,46 @@ fn from_ref() {
assert_eq!(*o, 2.0f64);
assert_eq!(f, 2.0f64);
}

#[cfg(feature = "arbitrary")]
mod arbitrary_test {
use super::{NotNan, OrderedFloat};
use arbitrary::{Arbitrary, Unstructured};

#[test]
fn exhaustive() {
// Exhaustively search all patterns of sign and exponent bits plus a few mantissa bits.
for high_bytes in 0..=u16::MAX {
let [h1, h2] = high_bytes.to_be_bytes();

// Each of these should not
// * panic,
// * return an error, or
// * need more bytes than given.
let n32: NotNan<f32> = Unstructured::new(&[h1, h2, h1, h2])
.arbitrary()
.expect("NotNan<f32> failure");
let n64: NotNan<f64> = Unstructured::new(&[h1, h2, h1, h2, h1, h2, h1, h2])
.arbitrary()
.expect("NotNan<f64> failure");
let _: OrderedFloat<f32> = Unstructured::new(&[h1, h2, h1, h2])
.arbitrary()
.expect("OrderedFloat<f32> failure");
let _: OrderedFloat<f64> = Unstructured::new(&[h1, h2, h1, h2, h1, h2, h1, h2])
.arbitrary()
.expect("OrderedFloat<f64> failure");

// Check for violation of NotNan's property of never containing a NaN.
assert!(!n32.into_inner().is_nan());
assert!(!n64.into_inner().is_nan());
}
}

#[test]
fn size_hints() {
assert_eq!(NotNan::<f32>::size_hint(0), (4, Some(4)));
assert_eq!(NotNan::<f64>::size_hint(0), (8, Some(8)));
assert_eq!(OrderedFloat::<f32>::size_hint(0), (4, Some(4)));
assert_eq!(OrderedFloat::<f64>::size_hint(0), (8, Some(8)));
}
}