Skip to content

Commit fd3583f

Browse files
kpreidmbrubeck
authored andcommitted
impl arbitrary::Arbitrary for NotNan and OrderedFloat.
1 parent 926bf53 commit fd3583f

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ serde = { version = "1.0", optional = true, default-features = false }
1919
rkyv = { version = "0.7", optional = true, default-features = false, features = ["size_32"] }
2020
schemars = { version = "0.6.5", optional = true }
2121
rand = { version = "0.8.3", optional = true, default-features = false }
22+
arbitrary = { version = "1.0.0", optional = true }
2223
proptest = { version = "1.0.0", optional = true }
2324

2425
[dev-dependencies]

src/lib.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,3 +1977,65 @@ mod impl_proptest {
19771977
}
19781978
impl_arbitrary! { f32, f64 }
19791979
}
1980+
1981+
#[cfg(feature = "arbitrary")]
1982+
mod impl_arbitrary {
1983+
use super::{FloatIsNan, NotNan, OrderedFloat};
1984+
use arbitrary::{Arbitrary, Unstructured};
1985+
use num_traits::FromPrimitive;
1986+
1987+
macro_rules! impl_arbitrary {
1988+
($($f:ident),+) => {
1989+
$(
1990+
impl<'a> Arbitrary<'a> for NotNan<$f> {
1991+
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1992+
let float: $f = u.arbitrary()?;
1993+
match NotNan::new(float) {
1994+
Ok(notnan_value) => Ok(notnan_value),
1995+
Err(FloatIsNan) => {
1996+
// If our arbitrary float input was a NaN (encoded by exponent = max
1997+
// value), then replace it with a finite float, reusing the mantissa
1998+
// bits.
1999+
//
2000+
// This means the output is not uniformly distributed among all
2001+
// possible float values, but Arbitrary makes no promise that that
2002+
// is true.
2003+
//
2004+
// An alternative implementation would be to return an
2005+
// `arbitrary::Error`, but that is not as useful since it forces the
2006+
// caller to retry with new random/fuzzed data; and the precendent of
2007+
// `arbitrary`'s built-in implementations is to prefer the approach of
2008+
// mangling the input bits to fit.
2009+
2010+
let (mantissa, _exponent, sign) =
2011+
num_traits::Float::integer_decode(float);
2012+
let revised_float = <$f>::from_i64(
2013+
i64::from(sign) * mantissa as i64
2014+
).unwrap();
2015+
2016+
// If this unwrap() fails, then there is a bug in the above code.
2017+
Ok(NotNan::new(revised_float).unwrap())
2018+
}
2019+
}
2020+
}
2021+
2022+
fn size_hint(depth: usize) -> (usize, Option<usize>) {
2023+
<$f as Arbitrary>::size_hint(depth)
2024+
}
2025+
}
2026+
2027+
impl<'a> Arbitrary<'a> for OrderedFloat<$f> {
2028+
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
2029+
let float: $f = u.arbitrary()?;
2030+
Ok(OrderedFloat::from(float))
2031+
}
2032+
2033+
fn size_hint(depth: usize) -> (usize, Option<usize>) {
2034+
<$f as Arbitrary>::size_hint(depth)
2035+
}
2036+
}
2037+
)*
2038+
}
2039+
}
2040+
impl_arbitrary! { f32, f64 }
2041+
}

tests/test.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,3 +692,46 @@ fn from_ref() {
692692
assert_eq!(*o, 2.0f64);
693693
assert_eq!(f, 2.0f64);
694694
}
695+
696+
#[cfg(feature = "arbitrary")]
697+
mod arbitrary_test {
698+
use super::{NotNan, OrderedFloat};
699+
use arbitrary::{Arbitrary, Unstructured};
700+
701+
#[test]
702+
fn exhaustive() {
703+
// Exhaustively search all patterns of sign and exponent bits plus a few mantissa bits.
704+
for high_bytes in 0..=u16::MAX {
705+
let [h1, h2] = high_bytes.to_be_bytes();
706+
707+
// Each of these should not
708+
// * panic,
709+
// * return an error, or
710+
// * need more bytes than given.
711+
let n32: NotNan<f32> = Unstructured::new(&[h1, h2, h1, h2])
712+
.arbitrary()
713+
.expect("NotNan<f32> failure");
714+
let n64: NotNan<f64> = Unstructured::new(&[h1, h2, h1, h2, h1, h2, h1, h2])
715+
.arbitrary()
716+
.expect("NotNan<f64> failure");
717+
let _: OrderedFloat<f32> = Unstructured::new(&[h1, h2, h1, h2])
718+
.arbitrary()
719+
.expect("OrderedFloat<f32> failure");
720+
let _: OrderedFloat<f64> = Unstructured::new(&[h1, h2, h1, h2, h1, h2, h1, h2])
721+
.arbitrary()
722+
.expect("OrderedFloat<f64> failure");
723+
724+
// Check for violation of NotNan's property of never containing a NaN.
725+
assert!(!n32.into_inner().is_nan());
726+
assert!(!n64.into_inner().is_nan());
727+
}
728+
}
729+
730+
#[test]
731+
fn size_hints() {
732+
assert_eq!(NotNan::<f32>::size_hint(0), (4, Some(4)));
733+
assert_eq!(NotNan::<f64>::size_hint(0), (8, Some(8)));
734+
assert_eq!(OrderedFloat::<f32>::size_hint(0), (4, Some(4)));
735+
assert_eq!(OrderedFloat::<f64>::size_hint(0), (8, Some(8)));
736+
}
737+
}

0 commit comments

Comments
 (0)