Skip to content

Commit

Permalink
impl arbitrary::Arbitrary for NotNan and OrderedFloat.
Browse files Browse the repository at this point in the history
  • Loading branch information
kpreid authored and mbrubeck committed Jan 5, 2022
1 parent 926bf53 commit fd3583f
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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)));
}
}

0 comments on commit fd3583f

Please sign in to comment.